Skip to content

Conversation

@ketkarameya
Copy link

@ketkarameya ketkarameya commented Feb 10, 2026

Code Review Bench PR redis#14562 - Clean up lookahead-related code

Benchmark PR for Gitar evaluation

Original PR: agentic-review-benchmarks#3

Clean up lookahead-related code


Summary by Gitar

This PR refactors Redis cluster slot handling to consolidate scattered logic and improve maintainability:

  • Centralizes slot extraction: extractSlotFromKeysResult() now returns three distinct values (INVALID_CLUSTER_SLOT, valid slot number, or CLUSTER_CROSSSLOT), eliminating duplicate validation in callers
  • Standardizes naming: Renames GETSLOT_CROSSSLOTCLUSTER_CROSSSLOT and GETSLOT_NOKEYSINVALID_CLUSTER_SLOT, moving them from server.h to cluster.h
  • Simplifies code: Reduces getSlotFromCommand() from 15+ lines to 6 lines by leveraging centralized logic
  • Eliminates magic numbers: Replaces hardcoded -1 comparisons with named constants throughout cluster_slot_stats.c and other files
  • Maintains backward compatibility: Preserves existing error handling via CLIENT_READ_CROSS_SLOT flag while unifying the underlying implementation

/* Allocate temporary buffer for slot tracking */
int *slot_buffer = malloc(sizeof(int) * keys_result->numkeys);

for (int j = 0; j <= keys_result->numkeys; j++) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Bug: Off-by-one: loop reads past end of keys array and slot_buffer

The loop condition j <= keys_result->numkeys iterates one time too many. When numkeys is N, this accesses keys_result->keys[N] which is out-of-bounds (valid indices are 0..N-1), and writes to slot_buffer[N] which is also out-of-bounds (buffer was allocated for exactly N elements).

This causes:

  1. Heap buffer overflow writing to slot_buffer[numkeys] (1 element past allocation)
  2. Out-of-bounds read of keys_result->keys[numkeys].pos, leading to undefined behavior — could crash, corrupt data, or read garbage producing incorrect slot calculations

This is a guaranteed bug for any command with 1+ keys.

Was this helpful? React with 👍 / 👎

Suggested change
for (int j = 0; j <= keys_result->numkeys; j++) {
for (int j = 0; j < keys_result->numkeys; j++) {
  • Apply suggested fix

for (int j = 0; j < keys_result->numkeys; j++) {

/* Allocate temporary buffer for slot tracking */
int *slot_buffer = malloc(sizeof(int) * keys_result->numkeys);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Quality: Unnecessary heap allocation: slot_buffer is written but never read

The slot_buffer array is allocated via malloc, populated in the loop, and then freed — but its contents are never read or used for any purpose. The cross-slot check is done entirely through the first_slot variable.

This introduces:

  1. An unnecessary malloc/free pair on every command execution — a hot path in Redis
  2. A potential memory leak if malloc returns NULL (no NULL check) — though on Linux with overcommit this is unlikely, it's still undefined behavior to write to a NULL pointer
  3. Additional complexity with no functional benefit

The entire slot_buffer allocation and writes should be removed.

Was this helpful? React with 👍 / 👎

Suggested change
int *slot_buffer = malloc(sizeof(int) * keys_result->numkeys);
int first_slot = INVALID_CLUSTER_SLOT;
for (int j = 0; j < keys_result->numkeys; j++) {
robj *this_key = argv[keys_result->keys[j].pos];
int this_slot = (int)keyHashSlot((char*)this_key->ptr, sdslen(this_key->ptr));
if (first_slot == INVALID_CLUSTER_SLOT)
first_slot = this_slot;
else if (first_slot != this_slot) {
return CLUSTER_CROSSSLOT;
}
}
return first_slot;
  • Apply suggested fix

@gitar-bot-staging
Copy link

gitar-bot-staging bot commented Feb 10, 2026

Code Review 🚫 Blocked 0 resolved / 3 findings

Clean refactoring of slot constants and logic consolidation, but extractSlotFromKeysResult() has a critical off-by-one heap buffer overflow (<= should be <) and an unnecessary malloc that is written to but never read.

🚨 Bug: Off-by-one: loop reads past end of keys array and slot_buffer

📄 src/cluster.c:1112

The loop condition j <= keys_result->numkeys iterates one time too many. When numkeys is N, this accesses keys_result->keys[N] which is out-of-bounds (valid indices are 0..N-1), and writes to slot_buffer[N] which is also out-of-bounds (buffer was allocated for exactly N elements).

This causes:

  1. Heap buffer overflow writing to slot_buffer[numkeys] (1 element past allocation)
  2. Out-of-bounds read of keys_result->keys[numkeys].pos, leading to undefined behavior — could crash, corrupt data, or read garbage producing incorrect slot calculations

This is a guaranteed bug for any command with 1+ keys.

Suggested fix
    for (int j = 0; j < keys_result->numkeys; j++) {
⚠️ Quality: Unnecessary heap allocation: slot_buffer is written but never read

📄 src/cluster.c:1110

The slot_buffer array is allocated via malloc, populated in the loop, and then freed — but its contents are never read or used for any purpose. The cross-slot check is done entirely through the first_slot variable.

This introduces:

  1. An unnecessary malloc/free pair on every command execution — a hot path in Redis
  2. A potential memory leak if malloc returns NULL (no NULL check) — though on Linux with overcommit this is unlikely, it's still undefined behavior to write to a NULL pointer
  3. Additional complexity with no functional benefit

The entire slot_buffer allocation and writes should be removed.

Suggested fix
    int first_slot = INVALID_CLUSTER_SLOT;

    for (int j = 0; j < keys_result->numkeys; j++) {
        robj *this_key = argv[keys_result->keys[j].pos];
        int this_slot = (int)keyHashSlot((char*)this_key->ptr, sdslen(this_key->ptr));

        if (first_slot == INVALID_CLUSTER_SLOT)
            first_slot = this_slot;
        else if (first_slot != this_slot) {
            return CLUSTER_CROSSSLOT;
        }
    }
    return first_slot;
💡 Quality: Tautological condition: else if (num_keys >= 0) is always true

📄 src/server.c:4141

After the if (num_keys < 0) { return; } branch, the else if (num_keys >= 0) condition is guaranteed to be true — it will never be false. This makes the code slightly confusing to read, as it suggests there could be a third case. A plain else (or removing the condition entirely and de-indenting the block) would be clearer.

The old code used else if (num_keys > 0) which was meaningful because it excluded the num_keys == 0 case. The change to >= 0 makes it equivalent to a simple else.

Suggested fix
    } else {
        /* Handle cross-slot keys: mark error and reset slot. */
        if (pcmd->slot == CLUSTER_CROSSSLOT) {
            pcmd->read_error = CLIENT_READ_CROSS_SLOT;
            pcmd->slot = INVALID_CLUSTER_SLOT;
        }
    }
Rules 🎸 2 actions taken

Gitar Rules

🎸 Linear Auto-Linking: Searched for related Linear issues; no match with >95% confidence found for this Redis cluster refactoring work
🎸 Summary Enhancement: Enhanced minimal PR description with 'Summary by Gitar' section containing 5 technical bullet points about cluster slot handling refactoring

4 rules not applicable. Show all rules by commenting gitar display:verbose.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants