3 Commits

Author SHA1 Message Date
Ray
a365e5617a Update clang_tidy.yml 2026-09-19 10:46:09 +02:00
250f2cf243 [gh actions] clang tidy linting: needless casts and assignments in if (#6163)
* [gh workflow] clang lint needless casts and assignments in loops

* [raudio, rcore, rmodels, rtext, rtextures]: Clang Lint: remove redudant casting

* [rlgl] remove needless casting

* [rshapes] EaseCubicInOut: move assignment statement out of if
2026-09-19 10:45:29 +02:00
16a8fb93f4 external/qoa: update vendored qoa.h with upstream out-of-bounds fixes (#6162)
Re-vendors src/external/qoa.h from phoboslab/qoa (f73b4a36). The bundled copy predated two upstream bounds fixes, so a crafted .qoa passed to LoadWaveFromMemory() could overflow the heap (frame samples not bounded against the allocation, qoa.h:640) and the stack (frame channel count not bounded against QOA_MAX_CHANNELS, qoa.h:615). Fixes #6161.


Claude-Session: https://claude.ai/code/session_01JfEozSeCHNgikWTQMDta5U

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-19 09:51:46 +02:00
9 changed files with 148 additions and 51 deletions

81
.github/workflows/clang_tidy.yml vendored Normal file
View File

@ -0,0 +1,81 @@
name: Clang Tidy Lints
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: clang-tidy-${{ github.ref }}
cancel-in-progress: true
jobs:
clang-tidy:
name: C lint
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install clang-tidy 19
run: |
sudo apt-get update
sudo apt-get install --yes clang-tidy-19
- name: Run clang-tidy
shell: bash
run: |
checks='-*,
bugprone-assignment-in-if-condition,
bugprone-infinite-loop,
bugprone-misplaced-widening-cast,
bugprone-multiple-statement-macro,
bugprone-suspicious-memory-comparison,
bugprone-suspicious-memset-usage,
readability-duplicate-include,
readability-redundant-casting,
readability-simplify-boolean-expr'
lint_log="$(mktemp)"
filtered_log="$(mktemp)"
lint_pattern='\[(bugprone|readability)-[^]]+\]'
echo "Using clang-tidy:"
clang-tidy-19 --version
echo
echo "Running clang-tidy..."
# Capture everything silently. Do not use tee here.
find src \
-path 'src/external' -prune \
-o -type f -name '*.c' -exec \
clang-tidy-19 \
--quiet \
--checks="$checks" \
--header-filter='.*' \
--exclude-header-filter='(^|.*/)external/.*' \
{} -- \
-std=c11 \
-Isrc \
-isystem src/external \
\; >"$lint_log" 2>&1 || true
# Retain only selected lint warnings, then remove any remaining
# diagnostics whose path passes through an external directory.
grep -E "$lint_pattern" "$lint_log" |
grep -Ev '(^|/)([^/]+/\.\./)*external/' \
>"$filtered_log" || true
if [[ -s "$filtered_log" ]]; then
echo "clang-tidy found the following lint violations:"
echo
cat "$filtered_log"
echo
echo "::error::clang-tidy found lint violations."
exit 1
fi
echo "clang-tidy found no selected lint violations."

36
src/external/qoa.h vendored
View File

@ -571,7 +571,10 @@ unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *q
qoa->channels = (frame_header >> 56) & 0x0000ff;
qoa->samplerate = (frame_header >> 32) & 0xffffff;
if (qoa->channels == 0 || qoa->samples == 0 || qoa->samplerate == 0) {
if (
qoa->channels == 0 || qoa->samples == 0 || qoa->samplerate == 0 ||
qoa->channels > QOA_MAX_CHANNELS
) {
return 0;
}
@ -580,7 +583,9 @@ unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *q
unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len) {
unsigned int p = 0;
*frame_len = 0;
if (frame_len) {
*frame_len = 0;
}
if (size < 8 + QOA_LMS_LEN * 4 * qoa->channels) {
return 0;
@ -593,15 +598,18 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa
unsigned int samples = (frame_header >> 16) & 0x00ffff;
unsigned int frame_size = (frame_header ) & 0x00ffff;
unsigned int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels;
unsigned int num_slices = data_size / 8;
unsigned int max_total_samples = num_slices * QOA_SLICE_LEN;
unsigned int header_size = 8 + QOA_LMS_LEN * 4 * channels;
unsigned int data_size = frame_size - header_size;
unsigned int max_total_slices = data_size / 8;
unsigned int num_slices = (samples + QOA_SLICE_LEN - 1) / QOA_SLICE_LEN;
if (
channels != qoa->channels ||
samplerate != qoa->samplerate ||
frame_size < header_size ||
frame_size > size ||
samples * channels > max_total_samples
num_slices > QOA_SLICES_PER_FRAME ||
num_slices * channels > max_total_slices
) {
return 0;
}
@ -645,7 +653,9 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa
}
}
*frame_len = samples;
if (frame_len) {
*frame_len = samples;
}
return p;
}
@ -655,9 +665,15 @@ short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) {
return NULL;
}
/* Calculate the required size of the sample buffer and allocate */
int total_samples = qoa->samples * qoa->channels;
/* Calculate the required size of the sample buffer and allocate, round up to full frames */
unsigned long long num_frames = ((unsigned long long)qoa->samples + QOA_FRAME_LEN - 1) / QOA_FRAME_LEN;
unsigned long long total_samples_ull = num_frames * QOA_FRAME_LEN * (unsigned long long)qoa->channels;
if (total_samples_ull > 0x7fffffff) { return NULL; }
unsigned int total_samples = (unsigned int)total_samples_ull;
short *sample_data = QOA_MALLOC(total_samples * sizeof(short));
if (!sample_data) { return NULL; }
unsigned int sample_index = 0;
unsigned int frame_len;
@ -670,7 +686,7 @@ short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) {
p += frame_size;
sample_index += frame_len;
} while (frame_size && sample_index < qoa->samples);
} while (frame_len == QOA_FRAME_LEN && sample_index < qoa->samples);
qoa->samples = sample_index;
return sample_data;

View File

@ -845,7 +845,7 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int
wave.sampleRate = info.sample_rate;
wave.sampleSize = 16; // By default, ogg data is 16 bit per sample (short)
wave.channels = info.channels;
wave.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData); // NOTE: It returns frames!
wave.frameCount = stb_vorbis_stream_length_in_samples(oggData); // NOTE: It returns frames!
wave.data = (short *)RL_CALLOC(wave.frameCount*wave.channels, sizeof(short));
// NOTE: Get the number of samples to process (be careful! asking for number of shorts, not bytes!)
@ -1394,7 +1394,7 @@ Music LoadMusicStream(const char *fileName)
music.stream = LoadAudioStream(info.sample_rate, 16, info.channels);
// WARNING: It seems this function returns length in frames, not samples, so multiply by channels
music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
music.frameCount = stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
music.looping = true; // Looping enabled by default
musicLoaded = true;
}
@ -1576,7 +1576,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data,
else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0))
{
// Open ogg audio stream
stb_vorbis *ctxOgg = stb_vorbis_open_memory((const unsigned char *)data, dataSize, NULL, NULL);
stb_vorbis *ctxOgg = stb_vorbis_open_memory(data, dataSize, NULL, NULL);
if (ctxOgg != NULL)
{
@ -1588,7 +1588,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data,
music.stream = LoadAudioStream(info.sample_rate, 16, info.channels);
// WARNING: It seems this function returns length in frames, not samples, so multiply by channels
music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
music.frameCount = stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
music.looping = true; // Looping enabled by default
musicLoaded = true;
}

View File

@ -4227,8 +4227,8 @@ float GetMouseWheelMove(void)
{
float result = 0.0f;
if (fabsf(CORE.Input.Mouse.currentWheelMove.x) > fabsf(CORE.Input.Mouse.currentWheelMove.y)) result = (float)CORE.Input.Mouse.currentWheelMove.x;
else result = (float)CORE.Input.Mouse.currentWheelMove.y;
if (fabsf(CORE.Input.Mouse.currentWheelMove.x) > fabsf(CORE.Input.Mouse.currentWheelMove.y)) result = CORE.Input.Mouse.currentWheelMove.x;
else result = CORE.Input.Mouse.currentWheelMove.y;
return result;
}

View File

@ -3517,8 +3517,8 @@ unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mi
}
else
{
if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize);
else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize);
if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, dataPtr + face*dataSize);
else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, dataPtr + face*dataSize);
}
#if defined(GRAPHICS_API_OPENGL_33)

View File

@ -4000,7 +4000,7 @@ void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float sca
{
Rectangle rec = { 0.0f, 0.0f, (float)texture.width, (float)texture.height };
DrawBillboardRec(camera, texture, rec, position, (Vector2){ scale*fabsf((float)rec.width/rec.height), scale }, tint);
DrawBillboardRec(camera, texture, rec, position, (Vector2){ scale*fabsf(rec.width/rec.height), scale }, tint);
}
// Draw a billboard (part of a texture defined by a rectangle)
@ -4069,10 +4069,10 @@ void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle rec, Vector3 p
}
Vector2 texcoords[4];
texcoords[0] = (Vector2){ (float)rec.x/texture.width, (float)(rec.y + rec.height)/texture.height };
texcoords[1] = (Vector2){ (float)(rec.x + rec.width)/texture.width, (float)(rec.y + rec.height)/texture.height };
texcoords[2] = (Vector2){ (float)(rec.x + rec.width)/texture.width, (float)rec.y/texture.height };
texcoords[3] = (Vector2){ (float)rec.x/texture.width, (float)rec.y/texture.height };
texcoords[0] = (Vector2){ rec.x/texture.width, (rec.y + rec.height)/texture.height };
texcoords[1] = (Vector2){ (rec.x + rec.width)/texture.width, (rec.y + rec.height)/texture.height };
texcoords[2] = (Vector2){ (rec.x + rec.width)/texture.width, rec.y/texture.height };
texcoords[3] = (Vector2){ rec.x/texture.width, rec.y/texture.height };
rlSetTexture(texture.id);
rlBegin(RL_QUADS);

View File

@ -4554,8 +4554,8 @@ Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2)
static float EaseCubicInOut(float t, float b, float c, float d)
{
float result = 0.0f;
if ((t /= 0.5f*d) < 1) result = 0.5f*c*t*t*t + b;
t /= 0.5f*d;
if (t < 1) result = 0.5f*c*t*t*t + b;
else
{
t -= 2;

View File

@ -1242,7 +1242,7 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f
DrawTextCodepoint(font, codepoint, (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint);
}
if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing);
if (font.glyphs[index].advanceX == 0) textOffsetX += (font.recs[index].width*scaleFactor + spacing);
else textOffsetX += ((float)font.glyphs[index].advanceX*scaleFactor + spacing);
}
@ -1313,7 +1313,7 @@ void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Ve
DrawTextCodepoint(font, codepoints[i], (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint);
}
if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing);
if (font.glyphs[index].advanceX == 0) textOffsetX += (font.recs[index].width*scaleFactor + spacing);
else textOffsetX += ((float)font.glyphs[index].advanceX*scaleFactor + spacing);
}
}
@ -1807,7 +1807,7 @@ char *TextReplace(const char *text, const char *search, const char *replacement)
// - 'text' points to the remainder of text after "end of replace"
while (count > 0)
{
insertPoint = (char *)strstr(text, search);
insertPoint = strstr(text, search);
lastReplacePos = (int)(insertPoint - text);
memcpy(tempPtr, text, lastReplacePos);
@ -1872,7 +1872,7 @@ char *TextReplaceAlloc(const char *text, const char *search, const char *replace
// - 'text' points to the remainder of text after "end of replace"
while (count > 0)
{
insertPoint = (char *)strstr(text, search);
insertPoint = strstr(text, search);
lastReplacePos = (int)(insertPoint - text);
memcpy(temp, text, lastReplacePos);
@ -2121,7 +2121,7 @@ int TextFindIndex(const char *text, const char *search)
if (text != NULL)
{
char *ptr = (char *)strstr(text, search);
char *ptr = strstr(text, search);
if (ptr != NULL) position = (int)(ptr - text);
}

View File

@ -623,8 +623,8 @@ Image LoadImageFromScreen(void)
{
Image image = { 0 };
image.width = (int)(GetRenderWidth());
image.height = (int)(GetRenderHeight());
image.width = GetRenderWidth();
image.height = GetRenderHeight();
image.mipmaps = 1;
image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
image.data = rlReadScreenPixels(image.width, image.height);
@ -1348,7 +1348,7 @@ void ImageFormat(Image *image, int newFormat)
for (int i = 0, k = 0; i < image->width*image->height*2; i += 2, k++)
{
((unsigned char *)image->data)[i] = (unsigned char)((pixels[k].x*0.299f + (float)pixels[k].y*0.587f + (float)pixels[k].z*0.114f)*255.0f);
((unsigned char *)image->data)[i] = (unsigned char)((pixels[k].x*0.299f + pixels[k].y*0.587f + pixels[k].z*0.114f)*255.0f);
((unsigned char *)image->data)[i + 1] = (unsigned char)(pixels[k].w*255.0f);
}
@ -1442,7 +1442,7 @@ void ImageFormat(Image *image, int newFormat)
for (int i = 0; i < image->width*image->height; i++)
{
((float *)image->data)[i] = (float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f);
((float *)image->data)[i] = (pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f);
}
} break;
case PIXELFORMAT_UNCOMPRESSED_R32G32B32:
@ -1476,7 +1476,7 @@ void ImageFormat(Image *image, int newFormat)
for (int i = 0; i < image->width*image->height; i++)
{
((unsigned short *)image->data)[i] = FloatToHalf((float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f));
((unsigned short *)image->data)[i] = FloatToHalf((pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f));
}
} break;
case PIXELFORMAT_UNCOMPRESSED_R16G16B16:
@ -1573,7 +1573,7 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co
{
if ((codepoint != ' ') && (codepoint != '\t'))
{
Rectangle rec = { (float)(textOffsetX + font.glyphs[index].offsetX), (float)(textOffsetY + font.glyphs[index].offsetY), (float)font.recs[index].width, (float)font.recs[index].height };
Rectangle rec = { (float)(textOffsetX + font.glyphs[index].offsetX), (float)(textOffsetY + font.glyphs[index].offsetY), font.recs[index].width, font.recs[index].height };
ImageDrawImagePro(&imText, font.glyphs[index].image, (Rectangle){ 0, 0, (float)font.glyphs[index].image.width, (float)font.glyphs[index].image.height },
rec, (Vector2){ 0 }, 0.0f, tint);
}
@ -1780,8 +1780,8 @@ void ImageResizeNN(Image *image, int newWidth, int newHeight)
Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color));
// EDIT: added +1 to account for an early rounding problem
int xRatio = (int)((image->width << 16)/newWidth) + 1;
int yRatio = (int)((image->height << 16)/newHeight) + 1;
int xRatio = ((image->width << 16)/newWidth) + 1;
int yRatio = ((image->height << 16)/newHeight) + 1;
int x2 = 0;
int y2 = 0;
@ -2277,10 +2277,10 @@ void ImageBlurGaussian(Image *image, int blurSize)
}
else if (pixelsCopy1[i].w <= 255.0f)
{
float alpha = (float)pixelsCopy1[i].w/255.0f;
pixels[i].r = (unsigned char)fminf((float)pixelsCopy1[i].x/alpha, 255.0);
pixels[i].g = (unsigned char)fminf((float)pixelsCopy1[i].y/alpha, 255.0);
pixels[i].b = (unsigned char)fminf((float)pixelsCopy1[i].z/alpha, 255.0);
float alpha = pixelsCopy1[i].w/255.0f;
pixels[i].r = (unsigned char)fminf(pixelsCopy1[i].x/alpha, 255.0);
pixels[i].g = (unsigned char)fminf(pixelsCopy1[i].y/alpha, 255.0);
pixels[i].b = (unsigned char)fminf(pixelsCopy1[i].z/alpha, 255.0);
pixels[i].a = (unsigned char) pixelsCopy1[i].w;
}
}
@ -2408,7 +2408,7 @@ void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize)
for (int i = 0; i < (image->width*image->height); i++)
{
float alpha = (float)imageCopy2[i].w;
float alpha = imageCopy2[i].w;
pixels[i].r = (unsigned char)((imageCopy2[i].x)*255.0f);
pixels[i].g = (unsigned char)((imageCopy2[i].y)*255.0f);
@ -3089,9 +3089,9 @@ Color *LoadImageColors(Image image)
} break;
case PIXELFORMAT_UNCOMPRESSED_R8G8B8:
{
pixels[i].r = (unsigned char)((unsigned char *)image.data)[k];
pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1];
pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2];
pixels[i].r = ((unsigned char *)image.data)[k];
pixels[i].g = ((unsigned char *)image.data)[k + 1];
pixels[i].b = ((unsigned char *)image.data)[k + 2];
pixels[i].a = 255;
k += 3;
@ -3332,9 +3332,9 @@ Color GetImageColor(Image image, int x, int y)
} break;
case PIXELFORMAT_UNCOMPRESSED_R8G8B8:
{
color.r = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3];
color.g = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3 + 1];
color.b = (unsigned char)((unsigned char *)image.data)[(y*image.width + x)*3 + 2];
color.r = ((unsigned char *)image.data)[(y*image.width + x)*3];
color.g = ((unsigned char *)image.data)[(y*image.width + x)*3 + 1];
color.b = ((unsigned char *)image.data)[(y*image.width + x)*3 + 2];
color.a = 255;
} break;
@ -5259,7 +5259,7 @@ Color ColorAlphaBlend(Color dst, Color src, Color tint)
else
{
unsigned int alpha = (unsigned int)src.a + 1; // Shifting by 8 (dividing by 256), so need to take that excess into account
result.a = (unsigned char)(((unsigned int)alpha*256 + (unsigned int)dst.a*(256 - alpha)) >> 8);
result.a = (unsigned char)((alpha*256 + (unsigned int)dst.a*(256 - alpha)) >> 8);
if (result.a > 0)
{