Skip to content

fix(cpp): reject input a scan loop cannot make progress on - #418

Closed
j-sperling wants to merge 1 commit into
j178:masterfrom
j-sperling:fix-scan-hang-on-malformed-input
Closed

fix(cpp): reject input a scan loop cannot make progress on#418
j-sperling wants to merge 1 commit into
j178:masterfrom
j-sperling:fix-scan-hang-on-malformed-input

Conversation

@j-sperling

Copy link
Copy Markdown
Contributor

Problem

The three scan loops in testutils/cpp/LC_IO.h hang forever on malformed or truncated input.

Each loop dispatches on is.peek() and, in its default: branch, extracts with operator>>. A failed extraction consumes nothing, so peek() returns the same byte on the next pass. The loops terminate only on ']', which malformed input never supplies — so the loop spins, allocating on every iteration.

Standalone reproducer, no leetgo workspace needed:

#include <sstream>
#include <vector>
#include "../LC_IO.h"

int main() {
    std::stringstream in("[1,2,3");  // truncated: no closing bracket
    std::vector<int> v;
    LeetCodeIO::scan(in, v);         // never returns
}

Helper::scan_list and Helper::scan_tree have the identical structure and the identical behaviour. [1,null,3] scanned as vector<int> hangs the same way, since n fails extraction in the array loop.

Fix

require_progress() checks at the top of each loop and exits with a diagnostic instead of spinning.

Three loops is complete coverage rather than partial: the scalar scan overloads either delegate to these loops or use >> / std::quoted, whose failures set failbit and surface at the enclosing loop's next check.

The check cannot fire on valid input — a well-formed array always has a ']' left to consume, and the trailing cin.ignore() calls in generated drivers run after the loop has already returned.

Why it is worth fixing

Beyond the hang itself, this is currently mis-reported. leetgo test shows malformed input as a three-second time limit exceeded, which sends users looking for a performance problem that does not exist. It also makes a gen/brute stress loop wedge silently, since a generator bug produces exactly this input.

Notes

  • Includes <cstdlib> and <iostream> explicitly rather than relying on <bits/stdc++.h> being pulled in first by generated code.
  • Verified with the CI command (g++ -std=c++17 -O2 -o tests tests.cpp && ./tests): all 15 existing tests pass.
  • No test added — exercising this kills the process, and tests/tests.cpp is single-process with no death-test facility. Happy to add a fork()-based test, or to switch the failure to a throw so it can be caught in-process, if you would prefer either.

Each of the three scan loops dispatches on is.peek() and, in its default
branch, extracts with operator>>. A failed extraction consumes nothing, so
peek() returns the same byte on the next pass. The loops terminate only on
']', which malformed input never supplies, so truncated input spins forever
and allocates on every iteration.

Reproducer, against an unpatched header:

    std::stringstream in("[1,2,3");  // no closing bracket
    std::vector<int> v;
    LeetCodeIO::scan(in, v);         // never returns

scan_list and scan_tree have the same structure and the same behaviour.

Checking for progress at the top of each loop turns the hang into an
immediate diagnostic on stderr. Valid input never reaches the check in a
failed state, because a well-formed array always has a ']' left to consume,
and the trailing ignore() calls in generated drivers run after the loop has
returned.

This also fixes a misleading report from `leetgo test`: malformed input
currently surfaces as a three-second time limit, sending users after a
performance problem that does not exist.
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.

1 participant