Fixes for issues found by cppcheck.

This commit is contained in:
Scott Duensing 2026-04-22 21:05:31 -05:00
parent 60d24c8c33
commit d9889b2fbb
11 changed files with 158 additions and 62 deletions

76
cppcheck.sh Executable file
View file

@ -0,0 +1,76 @@
#!/bin/bash
#
# cppcheck.sh -- static analysis for the DVX source tree.
#
# Usage:
# ./cppcheck.sh # whole tree
# ./cppcheck.sh src/widgets # one subtree
# ./cppcheck.sh src/libs/kpunch/texthelp/textHelp.c # one file
#
# Exits 0 even when warnings are found -- cppcheck's exit status is
# not useful as a gate for this codebase (too many DJGPP-isms it can't
# resolve). Read the output.
set -e
cd "$(dirname "$0")"
TARGETS="${@:-src}"
INCLUDES=(
-Isrc/libs/kpunch/libdvx
-Isrc/libs/kpunch/libdvx/platform
-Isrc/libs/kpunch/libdvx/thirdparty
-Isrc/libs/kpunch/libtasks
-Isrc/libs/kpunch/texthelp
-Isrc/libs/kpunch/listhelp
-Isrc/libs/kpunch/dvxshell
-Isrc/libs/kpunch/taskmgr
-Isrc/libs/kpunch/serial
-Isrc/widgets/kpunch
-Isrc/apps/kpunch/dvxbasic
-Isrc/apps/kpunch/dvxbasic/compiler
-Isrc/apps/kpunch/dvxbasic/runtime
-Isrc/apps/kpunch/dvxbasic/formrt
)
# DJGPP / platform macros cppcheck can't find. Expand them to nothing
# so the preprocessor leaves the code visible to the analyser instead
# of eliding whole functions behind unknown macro calls.
DEFINES=(
-DDXE_EXPORT=
-DDVX_WIDGET_IMPL
-D__DJGPP__=2
)
# Suppressions: things cppcheck flags that aren't actually wrong for
# this codebase. Keep the list short so real issues stay visible.
SUPPRESS=(
--suppress=missingIncludeSystem # system headers aren't on host
--suppress=unusedFunction # many public exports look "unused" to a .c-only scan
--suppress=constParameterPointer # noisy on this style
--suppress=constVariablePointer # ditto
--suppress=normalCheckLevelMaxBranches
# stb_ds arrput is a macro -- cppcheck can't see that the value
# (including heap pointers inside it) is stored, so it reports
# spurious null-derefs on the stb_ds dynamic-array handle.
--suppress=nullPointerRedundantCheck
--suppress=nullPointerArithmeticRedundantCheck
# Thirdparty headers -- their own style isn't our problem.
--suppress='*:src/libs/kpunch/libdvx/thirdparty/*'
--suppress='*:src/libs/kpunch/sql/thirdparty/*'
)
exec cppcheck \
--enable=warning,style,performance,portability \
--inline-suppr \
--std=c99 \
--platform=unix32 \
--quiet \
-j"$(nproc)" \
-i src/libs/kpunch/sql/thirdparty \
-i src/libs/kpunch/libdvx/thirdparty \
"${INCLUDES[@]}" \
"${DEFINES[@]}" \
"${SUPPRESS[@]}" \
$TARGETS

View file

@ -3991,7 +3991,7 @@ static void parsePrimary(BasParserT *p) {
} else {
basEmit8(&p->cg, OP_PUSH_INT32);
basEmit16(&p->cg, (int16_t)(val & 0xFFFF));
basEmit16(&p->cg, (int16_t)((val >> 16) & 0xFFFF));
basEmit16(&p->cg, (int16_t)(((uint32_t)val >> 16) & 0xFFFF));
}
advance(p);
return;

View file

@ -63,6 +63,7 @@
#define DEFAULT_FORM_W 400
#define DEFAULT_FORM_H 300
#define MAX_EVENT_NAME_LEN 128
#define MENU_ID_BASE 10000
// Module-level form runtime pointer for onFormClose callback
static BasFormRtT *sFormRt = NULL;
@ -1570,8 +1571,7 @@ void basFormRtHideForm(void *ctx, void *formRef) {
if (!form) {
basFormRtRuntimeError(rt,
"Hide on unknown form",
"The form reference resolved to NULL; check that the form name exists.",
NULL);
"The form reference resolved to NULL; check that the form name exists.");
return;
}
@ -1652,8 +1652,6 @@ static void *basFormRtLoadCfm(BasFormRtT *rt, const uint8_t *data, int32_t dataL
return form;
}
dvxLog("basFormRtLoadCfm: form '%s' created, %ld controls", form->name, (long)arrlen(form->controls));
// Set window callbacks (same as basFormRtLoadForm)
form->window->onClose = onFormClose;
form->window->onResize = onFormResize;
@ -1892,7 +1890,6 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
bar = wmAddMenuBar(form->window);
}
#define MENU_ID_BASE 10000
MenuT *menuStack[16];
memset(menuStack, 0, sizeof(menuStack));
bool topIsPopup = false;
@ -2291,7 +2288,6 @@ void basFormRtRemoveCtrl(void *ctx, void *formRef, const char *ctrlName) {
void basFormRtRunSimple(BasFormRtT *rt) {
if (!rt || !rt->vm) {
dvxLog("basFormRtRunSimple: no rt or vm");
return;
}
@ -2563,8 +2559,7 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
if (!form) {
basFormRtRuntimeError(rt,
"Show on unknown form",
"The form reference resolved to NULL; check that the form name exists.",
NULL);
"The form reference resolved to NULL; check that the form name exists.");
return;
}
@ -2765,7 +2760,7 @@ static BasFrmPopupMenuT *findPopupMenu(BasFormT *form, const char *name) {
// event dispatch. Starts from MENU_ID_BASE + the highest in-use ID
// so runtime-added items don't collide with .frm-loaded items.
static int32_t nextMenuItemId(BasFormT *form) {
int32_t maxId = 10000 - 1; // MENU_ID_BASE - 1
int32_t maxId = MENU_ID_BASE - 1;
for (int32_t i = 0; i < form->menuIdMapCount; i++) {
if (form->menuIdMap[i].id > maxId) {
@ -3665,12 +3660,6 @@ static void fireCtrlEvent(BasFormRtT *rt, BasControlT *ctrl, const char *eventNa
static void frmLoad_onCtrlBegin(void *userData, const char *typeName, const char *name) {
BasFrmLoadCtxT *ctx = (BasFrmLoadCtxT *)userData;
dvxLog("onCtrlBegin: type='%s' name='%s' form=%s nestDepth=%d",
typeName ? typeName : "?",
name ? name : "?",
(ctx->form && ctx->form->name[0]) ? ctx->form->name : "(null)",
(int)ctx->nestDepth);
if (!ctx->form || ctx->nestDepth <= 0) {
return;
}

View file

@ -1469,7 +1469,7 @@ static bool ideValidator_onFormBegin(void *ud, const char *name) {
IdeValidatorCtxT *v = (IdeValidatorCtxT *)ud;
IdeCtrlMapEntryT e;
memset(&e, 0, sizeof(e));
snprintf(e.name, BAS_MAX_CTRL_NAME, "%s", name);
snprintf(e.name, BAS_MAX_CTRL_NAME, "%s", name ? name : "");
snprintf(e.wgtType, BAS_MAX_CTRL_NAME, "%s", "Form");
arrput(v->entries, e);
snprintf(v->currentForm, BAS_MAX_CTRL_NAME, "%s", name ? name : "");
@ -1481,7 +1481,7 @@ static void ideValidator_onCtrlBegin(void *ud, const char *typeName, const char
IdeValidatorCtxT *v = (IdeValidatorCtxT *)ud;
IdeCtrlMapEntryT e;
memset(&e, 0, sizeof(e));
snprintf(e.name, BAS_MAX_CTRL_NAME, "%s", name);
snprintf(e.name, BAS_MAX_CTRL_NAME, "%s", name ? name : "");
snprintf(e.wgtType, BAS_MAX_CTRL_NAME, "%s", typeName ? typeName : "");
arrput(v->entries, e);

View file

@ -612,6 +612,7 @@ static const char *doWrap(char **wrappedPtr, int32_t *wrapWidthPtr, int32_t *lin
// Return cached result if width hasn't changed
if (*wrappedPtr && *wrapWidthPtr == pixelW) {
// cppcheck-suppress identicalInnerCondition
return *wrappedPtr;
}

View file

@ -58,6 +58,7 @@ const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name) {
// Entries are sorted at open() time so bsearch gives O(log n) lookup.
DvxResDirEntryT key;
memset(&key, 0, sizeof(key));
strncpy(key.name, name, sizeof(key.name) - 1);
key.name[sizeof(key.name) - 1] = '\0';
return (const DvxResDirEntryT *)bsearch(&key, h->entries, h->entryCount,

View file

@ -257,13 +257,8 @@ bool textEditFindNext(TextEditLineCacheT *lc, const char *buf, int32_t len, cons
int32_t cursorByte = textEditRowColToOff(lc, buf, len, *pCursorRow, *pCursorCol);
int32_t startPos = forward ? cursorByte + 1 : cursorByte - 1;
int32_t searchLen = len - needleLen + 1;
if (searchLen <= 0) {
return false;
}
int32_t count = forward ? (searchLen - startPos) : (startPos + 1);
int32_t searchLen = len - needleLen + 1; // >= 1 given the needleLen > len check above
int32_t count = forward ? (searchLen - startPos) : (startPos + 1);
if (count <= 0) {
return false;
@ -2080,7 +2075,6 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
if (*pCursor > 0) {
@ -2106,7 +2100,6 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
if (*pCursor < *pLen) {
@ -2134,7 +2127,6 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
*pCursor = newPos;
@ -2157,7 +2149,6 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
*pCursor = newPos;
@ -2178,7 +2169,6 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
*pCursor = 0;
@ -2199,7 +2189,6 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
*pCursor = *pLen;

View file

@ -276,9 +276,36 @@ static int dvxResAppendEntry(const char *path, const char *name, uint32_t type,
}
}
// Add new entry
entries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT));
data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
// Add new entry. Use tmp pointers so a realloc failure doesn't
// strand the existing blocks as unreachable leaks. Abort the
// write on OOM -- nothing useful to recover to here.
DvxResDirEntryT *newEntries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT));
if (!newEntries) {
for (uint32_t i = 0; i < count; i++) {
free(data[i]);
}
free(data);
free(entries);
return -1;
}
entries = newEntries;
uint8_t **newData = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
if (!newData) {
for (uint32_t i = 0; i < count; i++) {
free(data[i]);
}
free(data);
free(entries);
return -1;
}
data = newData;
memset(&entries[count], 0, sizeof(DvxResDirEntryT));
strncpy(entries[count].name, name, DVX_RES_NAME_MAX - 1);

View file

@ -244,9 +244,27 @@ static int cmdBuild(const char *dxePath, const char *manifestPath) {
newSize = (uint32_t)readLen;
}
// Append
entries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT));
data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
// Append. Use tmp pointers so a realloc failure doesn't
// strand the existing blocks as unreachable leaks. As a
// host build-time tool, abort on OOM -- nothing useful to
// recover to.
DvxResDirEntryT *newEntries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT));
if (!newEntries) {
fprintf(stderr, "dvxres: out of memory\n");
exit(1);
}
entries = newEntries;
uint8_t **newDataArr = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
if (!newDataArr) {
fprintf(stderr, "dvxres: out of memory\n");
exit(1);
}
data = newDataArr;
memset(&entries[count], 0, sizeof(DvxResDirEntryT));
snprintf(entries[count].name, DVX_RES_NAME_MAX, "%s", name);

View file

@ -200,6 +200,7 @@ void dataCtrlAddNew(WidgetT *w) {
d->isNewRow = true;
fireReposition(w);
// cppcheck-suppress memleak ; row.fields is stored into d->rows via arrput
}
@ -647,6 +648,7 @@ void dataCtrlRefresh(WidgetT *w) {
}
arrput(d->rows, row);
// cppcheck-suppress memleak ; row.fields is stored into d->rows via arrput
}
d->rowCount = (int32_t)arrlen(d->rows);
@ -830,7 +832,7 @@ void dataCtrlUpdate(WidgetT *w) {
pos += snprintf(sql + pos, sizeof(sql) - pos, "'%s'", escaped);
}
pos += snprintf(sql + pos, sizeof(sql) - pos, ")");
snprintf(sql + pos, sizeof(sql) - pos, ")");
} else {
// UPDATE table SET col1='val1', ... WHERE keyCol=keyVal
int32_t keyCol = findKeyCol(d);
@ -855,7 +857,7 @@ void dataCtrlUpdate(WidgetT *w) {
first = false;
}
pos += snprintf(sql + pos, sizeof(sql) - pos, " WHERE %s=%s", d->colNames[keyCol], keyVal);
snprintf(sql + pos, sizeof(sql) - pos, " WHERE %s=%s", d->colNames[keyCol], keyVal);
}
dvxSqlExec(db, sql);

View file

@ -30,10 +30,14 @@
// 2. TextArea -- multi-line text editor with row/col cursor,
// dual-axis scrolling, and full selection/clipboard support.
//
// Shared infrastructure (clipboard, multi-click detection, word
// boundary logic, cross-widget selection clearing, and the
// single-line editing engine widgetTextEditOnKey) lives in
// widgetCore.c so it can be linked from any widget DXE.
// Shared text-editing infrastructure lives in the texthelp library
// (src/libs/kpunch/texthelp/textHelp.c/h): clipboard, multi-click
// detection, word-boundary helpers, cross-widget selection clearing,
// the single-line edit engine, and the full multi-line edit engine
// (cache, paint, mouse/drag, key handler, find/replace, goto-line,
// get-word-at-cursor). This widget is a thin consumer that owns
// widget chrome (border, gutter, scrollbars, colorizer callbacks)
// and routes editing calls into the library via TextEditMultiT.
//
// All text editing is done in-place in fixed-size char buffers
// allocated at widget creation. No dynamic resizing -- this keeps
@ -51,22 +55,6 @@
// "low" end for deletion/copy is always min(start, end). The
// -1 sentinel means "no selection".
//
// Clipboard is a simple static buffer (4KB). This is a process-wide
// clipboard, not per-widget and not OS-integrated (DOS has no
// clipboard API). Text cut/copied from any widget is available to
// paste in any other widget.
//
// Multi-click detection uses clock() timestamps with a 500ms
// threshold and 4px spatial tolerance. Double-click selects word,
// triple-click selects line (TextArea) or all (TextInput).
//
// Cross-widget selection clearing: when a widget gains selection,
// clearOtherSelections() deselects any other widget that had an
// active selection. This prevents the confusing visual state of
// multiple selected ranges across different widgets. The tracking
// uses sLastSelectedWidget to achieve O(1) clearing rather than
// walking the entire widget tree.
//
// Masked input: a special TextInput mode where the buffer is
// pre-filled from a mask pattern (e.g., "###-##-####" for SSN).
// '#' accepts digits, 'A' accepts letters, '*' accepts any
@ -161,6 +149,11 @@ typedef struct {
// Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c)
#define CURSOR_BLINK_MS 250
// Default buffer sizes when the caller passes maxLen <= 0 and when
// the BASIC iface registers a control without specifying a size.
#define TEXTINPUT_DEFAULT_BUF 256
#define TEXTAREA_DEFAULT_BUF 65536
// sCursorBlinkOn is defined in widgetCore.c (shared state)
@ -1228,7 +1221,7 @@ WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen) {
}
w->data = ti;
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
int32_t bufSize = maxLen > 0 ? maxLen + 1 : TEXTINPUT_DEFAULT_BUF;
ti->buf = (char *)malloc(bufSize);
ti->bufSize = bufSize;
@ -1970,7 +1963,7 @@ static const WgtIfaceT sIfaceTextInput = {
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_INT,
.createArgs = { 256 },
.createArgs = { TEXTINPUT_DEFAULT_BUF },
.defaultEvent = "Change"
};
@ -1983,7 +1976,7 @@ static const WgtIfaceT sIfaceTextArea = {
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_INT,
.createArgs = { 65536 },
.createArgs = { TEXTAREA_DEFAULT_BUF },
.defaultEvent = "Change"
};