Skip to content

Accept negative numbers as option arguments in apps - #3425

Open
StevenLi-phoenix wants to merge 1 commit into
AOMediaCodec:mainfrom
StevenLi-phoenix:fix/1750-negative-option-values
Open

StevenLi-phoenix wants to merge 1 commit into
AOMediaCodec:mainfrom
StevenLi-phoenix:fix/1750-negative-option-values

Conversation

@StevenLi-phoenix

Copy link
Copy Markdown

Fixes #1750

Problem

NEXTARG() in apps/avifenc.c and apps/avifdec.c treated every argument that starts with - as the next option. So avifenc --jobs -10 in.png out.avif failed with --jobs requires an argument. (with no trailing newline), and no option ever got to check a negative value.

Fix

NEXTARG() now passes the next argument to the current option when the - is followed by a digit, as in -10. Option names (-o, --jobs), a bare - and a missing argument still give <option> requires an argument., which now ends with \n. A side effect: an option can now take a file name such as -1.avif (-o -1.avif).

Some options turned a negative value into a large unsigned value. They now reject it:

  • --jobs (avifenc and avifdec) and avifdec --index. --index -1 would otherwise mean all frames. --jobs 0 is still clamped to 1.
  • The avifenc options parsed by parseU32List(): --depth, --scaling-mode, --grid, --cicp, --pasp, --crop and --clli. Without this, --cicp -1/-1/-1 wrote color primaries 65535 and --pasp -1,1 wrote an hSpacing of 4294967295. --clap still takes negative values, because its horizontal and vertical offsets are signed (ISO/IEC 14496-12:2022, 12.1.4.1). It calls parseIntList() with allowNegative, and avifCropRectFromCleanApertureBox() still rejects a negative width, height or denominator.
  • avifenc --irot and --imir. The value is range-checked as an int before the cast to uint8_t, so --irot -253 is not read as 3.
  • avifdec --size-limit and --dimension-limit, which now reject a leading -. strtoul("-1") returns ULONG_MAX, which equals UINT32_MAX where unsigned long is 32 bits (Windows), so -1 would have been accepted there as the largest limit.

Two kinds of command lines that work on main now fail. Both wrote wrapped values:

  • a negative value that is not the first one in a parseU32List() option, for example --cicp 1/-1/1 (transfer characteristics 65535) or --pasp 1,-1;
  • --irot 256 to 259 and --imir 256 or 257, which were read modulo 256 (--irot 259 as 3).

Not changed:

  • Options that clamp out-of-range values (-q, --speed, --min/--max, --tilerowslog2, avifdec -q/--png-compress, ...) clamp negative values too. avifenc -k -3 behaves like -k 0, which means no maximum keyframe interval.
  • Values outside the int range still overflow in atoi(), as on main, for positive and negative input alike.
  • tests/avifyuv.c keeps its own copy of the old NEXTARG(), for its -m option, because it is a test tool.

For the maintainer: a negative --jobs could be clamped to 1 instead, as 0 is and as avifgainmaputil's JobsConverter does. I made it an error because the issue asks for a meaningful error message. I can switch it.

Testing

New assertions in tests/test_cmd.sh:

  • These must fail with their ERROR: message: avifenc --jobs -10, --cicp -1/-1/-1, --pasp -1,1, --irot -253 and --imir -255; avifdec --jobs -10, --index -1 and --size-limit -1.
  • --jobs -o and --jobs -i must still report --jobs requires an argument.
  • --clap 100,1,100,1,-10,1,-20,1 must still encode, and avifdec --info must show hOff: -10/1, vOff: -20/1.

Build (macOS arm64, Apple clang, system aom 3.15.2 and dav1d 1.5.4):

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DAVIF_CODEC_AOM=SYSTEM -DAVIF_CODEC_DAV1D=SYSTEM \
  -DAVIF_LIBYUV=OFF -DAVIF_BUILD_APPS=ON -DAVIF_BUILD_TESTS=ON -DAVIF_GTEST=LOCAL -DAVIF_ENABLE_WERROR=ON
ninja -C build
  • With main's avifenc and avifdec, the new test_cmd.sh fails at grep 'ERROR: Invalid number of jobs: -10', because avifenc prints --jobs requires an argument.
  • With only the NEXTARG(), --jobs and --index changes, it fails because avifenc --cicp -1/-1/-1 exits 0.
  • With this change, ctest --test-dir build -R '^test_cmd' passes 11 of 11 tests, and the full ctest --test-dir build -j 8 passes 62 of 62. test_cmd_stdin is not built in this configuration because AVIF_ENABLE_JPEG_GAIN_MAP_CONVERSION is off.
  • clang-format 19.1.7 (-style=file --dry-run --Werror) reports nothing for apps/avifenc.c and apps/avifdec.c.

I did not test on Windows or Linux. On macOS, unsigned long is 64 bits, so --size-limit -1 also fails without the new leading-- check.

NEXTARG() in avifenc and avifdec treated any argument that starts with
'-' as the next option, so "avifenc --jobs -10" failed with
"--jobs requires an argument." An argument where '-' is followed by a
digit is now passed to the option, which validates it. Also end the
"requires an argument." message with a newline.

Reject the negative values that options would otherwise turn into large
unsigned values:
- --jobs in avifenc and avifdec, and avifdec --index (-1 would mean
  all frames).
- The avifenc options parsed by parseU32List(), such as --cicp and
  --pasp. --clap still accepts negative values because its horizontal
  and vertical offsets are signed.
- avifenc --irot and --imir. The value is now range-checked before the
  cast to uint8_t, so --irot 259 is no longer read as 3.
- avifdec --size-limit and --dimension-limit, where strtoul() returns
  UINT32_MAX for -1 if unsigned long is 32 bits.

Fixes AOMediaCodec#1750.

Co-authored-by: Claude Opus-5-5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NEXTARG() doesn't allow the argument of a command-line option to be a negative number

2 participants