Skip to content

--threshold accepts NaN, infinity, and values outside [0, 1] #21

Open

Description

@Mazinkam

Summary

--threshold accepts non-finite and out-of-range floats. nan admits even a pair with zero similarity; inf, 1e309, and 1.1 suppress even exact matches. This silently changes results instead of reporting invalid configuration.

Verified against master at 66ff6d21a42c04afcad89c78a80066176d1294b0 (dryer 0.1.0), using Python 3.14.8.

Minimal reproduction

Requires Git and Python >=3.11. No package installation, parser downloads, or native dependencies are needed. Run in a fresh disposable checkout:

work=$(mktemp -d)
git clone https://github.com/unclebob/dryer.git "$work/dryer"
cd "$work/dryer"
git checkout 66ff6d21a42c04afcad89c78a80066176d1294b0
python3 -B - <<'PY'
"""Run from a dryer checkout with Python >=3.11; standard library only."""
import ast
from pathlib import Path
import sys
import types

sys.dont_write_bytecode = True
sys.path.insert(0, str(Path('src').resolve()))

def load(name, file, omit):
    path = Path(file)
    tree = ast.parse(path.read_text())
    tree.body = [n for n in tree.body if not (isinstance(n, ast.ImportFrom) and n.module == omit)]
    module = types.ModuleType(name)
    sys.modules[name] = module
    exec(compile(tree, str(path), 'exec'), module.__dict__)
    return module

# Only parser-dependent imports are omitted; argument parsing and pair
# comparison below execute the unchanged source functions.
cli = load('dryer_cli_probe', 'src/dryer/cli.py', 'dryer.scan')
scan = load('dryer_scan_probe', 'src/dryer/scan.py', 'dryer.extract')
from dryer.model import Entry

left = Entry('python', 'a.py', 1, 4, 20, frozenset({'a'}))
disjoint = Entry('python', 'b.py', 1, 4, 20, frozenset({'b'}))
identical = Entry('python', 'c.py', 1, 4, 20, frozenset({'a'}))
for value in ('nan', 'inf', 'Infinity', '1e309', '1.1', ' -0.1', ' -inf'):
    options = cli.parse_args(['--threshold', value])
    assert options.action == 'scan', value
    unrelated = scan.find_duplicates([left, disjoint], options.threshold)
    exact = scan.find_duplicates([left, identical], options.threshold)
    print(f'{value!r}: action={options.action}, threshold={options.threshold}, disjoint_pairs={len(unrelated)}, exact_pairs={len(exact)}')
    expected = (1, 1) if value in ('nan', ' -0.1', ' -inf') else (0, 0)
    assert (len(unrelated), len(exact)) == expected
for value in ('-0.1', '-inf'):
    options = cli.parse_args(['--threshold', value])
    print(f'{value!r}: action={options.action}, exit={options.exit_code}')
    assert options.exit_code == 1
for value in ('0', '0.82', '1'):
    assert cli.parse_args(['--threshold', value]).action == 'scan'
PY

This source-level probe executes the original functions through Python AST loading, omitting only parser-dependent imports. It does not replace any exercised function or run the installed CLI/parser end to end. Its assertions verify the observed defect, not the expected fixed behavior.

Actual result

'nan': action=scan, threshold=nan, disjoint_pairs=1, exact_pairs=1
'inf': action=scan, threshold=inf, disjoint_pairs=0, exact_pairs=0
'Infinity': action=scan, threshold=inf, disjoint_pairs=0, exact_pairs=0
'1e309': action=scan, threshold=inf, disjoint_pairs=0, exact_pairs=0
'1.1': action=scan, threshold=1.1, disjoint_pairs=0, exact_pairs=0
' -0.1': action=scan, threshold=-0.1, disjoint_pairs=1, exact_pairs=1
' -inf': action=scan, threshold=-inf, disjoint_pairs=1, exact_pairs=1
'-0.1': action=help, exit=1
'-inf': action=help, exit=1

Expected result

Reject non-finite thresholds and thresholds outside the inclusive interval [0, 1] as usage errors before selection or scanning. Preserve valid endpoints 0 and 1.

Cause

_number() only calls float(), and argument parsing assigns that result without bounds validation. find_duplicates() skips pairs only when score < threshold; this comparison is false for NaN.

Bare -0.1 and -inf are already rejected by _take() because they begin with -. However, quoted values with leading whitespace (" -0.1", " -inf") pass _take() and are accepted by float(). The reproduction distinguishes these cases rather than claiming all negative spellings are accepted.

Suggested regression test

Add parameterized parser tests rejecting nan, NaN, inf, Infinity, 1e309, 1.1, and whitespace-prefixed negative values, with a nonzero usage status before scanning. Assert 0, 0.82, and 1 still parse successfully. The existing threshold test covers only a nonnumeric string.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions