Skip to content

[rust-guard] Rust Guard: Replace heap-allocating to_lowercase() with eq_ignore_ascii_case in is_trusted_first_party_bot #3314

@github-actions

Description

@github-actions

🦀 Rust Guard Improvement Report

Improvement 1: Avoid Heap Allocation in is_trusted_first_party_bot

Category: Performance (WASM-specific) / Type Safety & Idioms
File(s): guards/github-guard/rust-guard/src/labels/helpers.rs
Effort: Small (< 15 min)
Risk: Low

Problem

is_trusted_first_party_bot (line 1294) calls username.to_lowercase() before performing its comparisons. This allocates a new String on the heap on every single call — even when the input is already lowercase ASCII. The function is called on every PR, issue, and commit integrity decision.

This is inconsistent with the established pattern elsewhere in the same file: username_in_list (line 248) uses eq_ignore_ascii_case, which is a zero-allocation in-place comparison. All bot names in the hardcoded list are pure ASCII lowercase, so eq_ignore_ascii_case is semantically identical but allocation-free.

Before

// labels/helpers.rs:1294-1305
pub fn is_trusted_first_party_bot(username: &str) -> bool {
    let lower = username.to_lowercase();     // heap allocation every call
    lower == "dependabot[bot]"
        || lower == "github-actions[bot]"
        || lower == "github-actions"
        || lower == "app/github-actions"
        || lower == "github-merge-queue[bot]"
        || lower == "copilot"
        || lower == "copilot-swe-agent[bot]"
        || lower == "copilot-swe-agent"
        || lower == "app/copilot-swe-agent"
}

After

pub fn is_trusted_first_party_bot(username: &str) -> bool {
    username.eq_ignore_ascii_case("dependabot[bot]")
        || username.eq_ignore_ascii_case("github-actions[bot]")
        || username.eq_ignore_ascii_case("github-actions")
        || username.eq_ignore_ascii_case("app/github-actions")
        || username.eq_ignore_ascii_case("github-merge-queue[bot]")
        || username.eq_ignore_ascii_case("copilot")
        || username.eq_ignore_ascii_case("copilot-swe-agent[bot]")
        || username.eq_ignore_ascii_case("copilot-swe-agent")
        || username.eq_ignore_ascii_case("app/copilot-swe-agent")
}

Why This Matters

  • Zero allocation: eq_ignore_ascii_case compares byte-by-byte in place — no String is created or dropped.
  • WASM budget: In WASM, every heap allocation goes through the linear memory allocator. Avoiding it on a hot path matters.
  • Consistency: The existing helper username_in_list already uses eq_ignore_ascii_case for case-insensitive matching; this brings is_trusted_first_party_bot in line with that pattern.
  • Correctness preserved: All bot name literals are pure ASCII lowercase, so eq_ignore_ascii_case produces identical results to to_lowercase() + ==.

Improvement 2: Remove Stale #[allow(dead_code)] from Logging API in lib.rs

Category: Dead Code
File(s): guards/github-guard/rust-guard/src/lib.rs
Effort: Small (< 15 min)
Risk: Low

Problem

Six items in lib.rs carry #[allow(dead_code)] even though they are all actively used:

Item Line Why the attribute is stale
invoke_backend 99 pub fn — Rust never emits dead_code for public items; additionally, it's referenced as crate::invoke_backend in labels/backend.rs:85,384,388,546
LogLevel enum 137 All four variants are used by the log_xxx helpers below
log_debug 156 Called via crate::log_debug() in labels/backend.rs and labels/helpers.rs
log_info 161 Called via crate::log_info() in labels/backend.rs, labels/helpers.rs, labels/tool_rules.rs
log_warn 166 Called via crate::log_warn() in labels/backend.rs
log_error 171 Called via crate::log_error() in labels/backend.rs

Suppressing dead_code on live code is misleading: it implies the items might be dead (or were dead at some point), making reviewers wonder whether they're safe to delete. It also disables the compiler's ability to alert on a future regression where the functions genuinely become unused.

Before

// lib.rs
#[allow(dead_code)]
pub fn invoke_backend(tool_name: &str, args_json: &str, result_buffer: &mut [u8]) -> Result<usize, i32> { ... }

#[repr(u32)]
#[allow(dead_code)]
pub enum LogLevel { Debug = 0, Info = 1, Warn = 2, Error = 3 }

#[allow(dead_code)]
fn log_debug(msg: &str) { log(LogLevel::Debug, msg); }

#[allow(dead_code)]
fn log_info(msg: &str) { log(LogLevel::Info, msg); }

#[allow(dead_code)]
fn log_warn(msg: &str) { log(LogLevel::Warn, msg); }

#[allow(dead_code)]
fn log_error(msg: &str) { log(LogLevel::Error, msg); }

After

pub fn invoke_backend(tool_name: &str, args_json: &str, result_buffer: &mut [u8]) -> Result<usize, i32> { ... }

#[repr(u32)]
pub enum LogLevel { Debug = 0, Info = 1, Warn = 2, Error = 3 }

fn log_debug(msg: &str) { log(LogLevel::Debug, msg); }
fn log_info(msg: &str) { log(LogLevel::Info, msg); }
fn log_warn(msg: &str) { log(LogLevel::Warn, msg); }
fn log_error(msg: &str) { log(LogLevel::Error, msg); }

Why This Matters

  • Clarity: Removing the annotations signals clearly that these items are live and intentional, not leftover scaffolding.
  • Compiler safety net restored: If a future refactor stops calling any of these, the compiler will immediately flag it — but only if #[allow(dead_code)] isn't there to muffle the warning.
  • Consistency: Items that are genuinely conditionally-dead use #[cfg_attr(not(test), allow(dead_code))] (e.g., CollaboratorPermission::login in backend.rs:74). Plain #[allow(dead_code)] should be reserved for that pattern, not unconditionally live code.

Codebase Health Summary

  • Total Rust files: 10
  • Total lines: 10,934
  • Areas analyzed: lib.rs, tools.rs, labels/mod.rs, labels/helpers.rs, labels/backend.rs, labels/tool_rules.rs, labels/response_items.rs, labels/response_paths.rs, labels/constants.rs
  • Areas with no further improvements found (this run): none — first analysis run

Generated by Rust Guard Improver • Run: §24074493118

Generated by Rust Guard Improver · ● 840.4K ·

  • expires on Apr 14, 2026, 9:38 AM UTC

Metadata

Metadata

Assignees

No one assigned

    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