Skip to content

Claude Code Review Report #590

@qarmin

Description

@qarmin

Recently, I ran Claude Code across several projects(like image-rs, lofty of symphonia) to look for potential issues.

In this projects, a significant portion of reported findings turned out to be incorrect or minor false positives, but a non-trivial subset was valid and included real issues ranging from incorrect comments to actual logic bugs. As a result, the findings generally require manual validation to separate noise from actionable problems.

Full Reports (findings-interactive.html - interactive report view for manual inspection, findings-short.md - compact list ready to copy into GitHub, findings-table.html - compact tabular report version):

findings-interactive.html
findings-short.md
findings-table.html

Example findings (this is only a subset of the findings, specifically those most likely to be actual bugs; for the full list, see the reports above):

=================================

API_1 HIGH

Description: GLDevice::create_texture_from_data calls gl::TexImage2D without a trailing ck() error check, unlike the other GL calls in the function. If TexImage2D fails (e.g. invalid enum/format combination), the error is silently swallowed and only surfaces on the next checked call.

Locations:

gl/src/lib.rs:386-395
  386 |             gl::TexImage2D(gl::TEXTURE_2D,
  387 |                            0,
  388 |                            format.gl_internal_format(),
  389 |                            size.x() as GLsizei,
  390 |                            size.y() as GLsizei,
  391 |                            0,
  392 |                            format.gl_format(),
  393 |                            format.gl_type(),
  394 |                            data_ptr)
  395 |         }

Fix: Append ; ck(); after the TexImage2D call (matching the pattern used in create_texture).

API_5 LOW

Description: gpu_data::RenderCommand::UploadTexelData carries Arc<Vec<ColorU>>, but in GradientTileBuilder::create_render_commands a fresh Arc::new(tile.texels) is created per gradient tile, defeating the point of Arc (no sharing actually occurs). A Box<[ColorU]> or plain Vec would be equivalent.

Locations:

renderer/src/paint.rs:867-877
  867 |     fn create_render_commands(self, render_commands: &mut Vec<RenderCommand>) {
  868 |         for tile in self.tiles {
  869 |             render_commands.push(RenderCommand::UploadTexelData {
  870 |                 texels: Arc::new(tile.texels),
  871 |                 location: TextureLocation {
  872 |                     rect: RectI::new(vec2i(0, 0), Vector2I::splat(GRADIENT_TILE_LENGTH as i32)),
  873 |                     page: tile.page,
  874 |                 },
  875 |             });
  876 |         }
  877 |     }

Fix: Either drop the Arc wrapper from UploadTexelData or actually share data.

ERR_1 HIGH

Description: fill_or_stroke_text explicitly drops the Result returned by push_layout, silently swallowing GlyphLoadingError. The TODO comment confirms this is a known omission. Failed glyph rasterization produces no rendered text and no diagnostic.

Locations:

canvas/src/text.rs:81-96
   81 |         // TODO(pcwalton): Report errors.
   82 |         drop(self.canvas_font_context
   83 |                  .0
   84 |                  .borrow_mut()
   85 |                  .font_context
   86 |                  .push_layout(&mut self.canvas.scene,
   87 |                               &layout.skribo_layout,
   88 |                               &TextStyle { size: layout.font_size },
   89 |                               &FontRenderOptions {
   90 |                                   transform,
   91 |                                   render_mode,
   92 |                                   hinting_options: HintingOptions::None,
   93 |                                   clip_path,
   94 |                                   blend_mode,
   95 |                                   paint_id,
   96 |                               }));

Fix: Propagate the Result up to the caller, or at minimum log the error.

LOGIC_1 CRITICAL

Description: GLDevice::create_texture_from_data() hard-codes the GLTexture.format field to TextureFormat::R8 regardless of the actual format parameter. The OpenGL upload uses the correct format, but the returned GLTexture wrongly reports R8 as its format. This causes every later operation that reads texture.format to misbehave — RGBA8 dummy/font textures will be reported as R8.

Locations:

gl/src/lib.rs:379-399
  379 |     fn create_texture_from_data(&self, format: TextureFormat, size: Vector2I, data: TextureDataRef)
  380 |                                 -> GLTexture {
  381 |         let data_ptr = data.check_and_extract_data_ptr(size, format);
  382 |         let mut texture = GLTexture { gl_texture: 0, size, format: TextureFormat::R8 };
  383 |         unsafe {
  384 |             gl::GenTextures(1, &mut texture.gl_texture); ck();
  385 |             self.bind_texture(&texture, 0);
  386 |             gl::TexImage2D(gl::TEXTURE_2D,
  387 |                            0,
  388 |                            format.gl_internal_format(),
  389 |                            size.x() as GLsizei,
  390 |                            size.y() as GLsizei,
  391 |                            0,
  392 |                            format.gl_format(),
  393 |                            format.gl_type(),
  394 |                            data_ptr)
  395 |         }
  396 | 
  397 |         self.set_texture_sampling_mode(&texture, TextureSamplingFlags::empty());
  398 |         texture
  399 |     }

Fix: Replace format: TextureFormat::R8 with format (the function argument). Also add a ck() call after the gl::TexImage2D.

LOGIC_2 CRITICAL

Description: Metal upload_to_texture() computes the source buffer byte offset using src_stride (rect.width()*bpp) but the staging buffer was laid out using dest_stride (texture_size.x()*bpp). When uploading a sub-rectangle whose width differs from texture_size.x(), the GPU read offset will be wrong and the texture will receive garbage rows.

Locations:

metal/src/lib.rs:793-808
  793 |         let dest_origin = MTLOrigin { x: rect.origin_x() as u64, y: rect.origin_y() as u64, z: 0 };
  794 |         let dest_byte_offset = rect.origin_y() as u64 * src_stride as u64 +
  795 |             rect.origin_x() as u64 * bytes_per_pixel as u64;
  796 | 
  797 |         let blit_command_encoder = command_buffer.real_new_blit_command_encoder();
  798 |         blit_command_encoder.copy_from_buffer_to_texture(&src_shared_buffer,
  799 |                                                          dest_byte_offset,
  800 |                                                          dest_stride,
  801 |                                                          0,
  802 |                                                          src_size,
  803 |                                                          &dest_texture.private_texture,
  804 |                                                          0,
  805 |                                                          0,
  806 |                                                          dest_origin,
  807 |                                                          MTLBlitOption::empty());
  808 |         blit_command_encoder.end_encoding();

Fix: Change to let dest_byte_offset = rect.origin_y() as u64 * dest_stride + rect.origin_x() as u64 * bytes_per_pixel; (use dest_stride, not src_stride).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions