java-topology/docs/tickets/weechat-0001-irc-nick-search-linear-scan-protocol-handlers.md

2.6 KiB
Raw Blame History

weechat-0001: irc_nick_search() O(n) called per-channel in AWAY/NICK/QUIT/KILL handlers

Target: weechat/weechat Severity: HIGH CWE: CWE-407 (Inefficient Algorithmic Complexity) File: src/plugins/irc/irc-protocol.c (multiple handlers) Status: PATCHED

Description

irc_nick_search() performs an O(n) linear scan of the channel->nicks linked list (irc-nick.c:830848). It is called inside an outer loop over all channels the server knows about in four hot IRC protocol handlers:

Handler File:Line Pattern
AWAY irc-protocol.c:648651 for each channel: irc_nick_search(nick)
NICK irc-protocol.c:22952366 for each channel: irc_nick_search(old_nick)
QUIT irc-protocol.c:33973408 for each channel: irc_nick_search(nick)
KILL irc-protocol.c:20512055 for each channel: irc_nick_search(nick) × 2

All four are O(C × N) where C = channels on server, N = nicks per channel.

Root cause

// irc-nick.c:830 — O(n) linked-list walk
struct t_irc_nick *
irc_nick_search(struct t_irc_server *server, struct t_irc_channel *channel,
                const char *nickname) {
    for (ptr_nick = channel->nicks; ptr_nick; ptr_nick = ptr_nick->next_nick) {
        if (irc_server_strcasecmp(server, ptr_nick->name, nickname) == 0)
            return ptr_nick;
    }
    return NULL;
}

channel->nicks is a plain doubly-linked list with no hash index. The handlers iterate all channels, calling irc_nick_search for each:

// irc-protocol.c:648 — AWAY handler — O(C × N)
for (ptr_channel = ctxt->server->channels; ptr_channel; ptr_channel = ptr_channel->next_channel) {
    ptr_nick = irc_nick_search(ctxt->server, ptr_channel, ctxt->nick);  // O(N)
    ...
}

On a busy server with 200 channels of 500 nicks each, a single AWAY message triggers 100,000 strcmp operations.

Fix

Add a GHashTable* nicks_hashtable (nick_name → t_irc_nick*) to t_irc_channel. Maintain it in sync with channel->nicks on add/remove. Replace irc_nick_search with a hash lookup.

// O(1) lookup
struct t_irc_nick *
irc_nick_search(struct t_irc_server *server, struct t_irc_channel *channel,
                const char *nickname) {
    if (channel->nicks_hashtable)
        return weechat_hashtable_get(channel->nicks_hashtable, lowercase(nickname));
    // fallback for empty/initializing channel
    ...
}

Ops/ns numbers (Java benchmark)

See defects/weechat/unit/WeechatTest.java. At C=200 channels, N=500 nicks: slow ~100,000 ops, fast ~200 ops → ~500× speedup.