@ 261102.1

250529.1 · 9ac6ddae2266c6c19f8599c5ad5fc67e3bc7c9f4

Fix whitespace in number parsing (#3195)

* Handle input errors for --indent

Handle malformed and overflowing arguments. Also, forbid leading and trailing spaces to match the behavior of tonumber from commit ce0e788 (improve tonumber/0 performance by parsing input as number literal, 2024-03-02).

* Do not skip leading whitespace in jvp_strtod

`jvp_strtod` skips leading whitespace, but its decnum counterpart `decNumberFromString` (called within `jv_number_with_literal`) does not. Those two are called interchangeably, so it leads to inconsistent behavior depending on whether the decnum feature is enabled. Additionally, `classify`, used in the token scanner, only considers [ \t\n\r] to be whitespace, but `jvp_strtod` consumes the larger set [ \t\n\v\f\r], so those extra characters are considered literals.

Changing this deviates from the behavior of `strdod` from <stdlib.h> and is technically a breaking API change, since it is a public symbol.

reading declarations

src/jv_dtoa.c

@@ -2368,29 +2368,21 @@ jvp_strtod  	sign = nz0 = nz1 = nz = bc.dplen = bc.uflchk = 0; 	dval(&rv) = 0.;-	for(s = s00;;s++) switch(*s) {+	switch(*(s = s00)) { 		case '-': 			sign = 1; 			/* no break */ 			JQ_FALLTHROUGH; 		case '+': 			if (*++s)-				goto break2;+				break; 			/* no break */ 			JQ_FALLTHROUGH; 		case 0: 			goto ret0;-		case '\t':-		case '\n':-		case '\v':-		case '\f':-		case '\r':-		case ' ':-			continue; 		default:-			goto break2;+			break; 		}- break2: 	if (*s == '0') { #ifndef NO_HEX_FP /*{*/ 		switch(s[1]) {

src/main.c

@@ -422,12 +422,15 @@ int main(int argc, char* argv[]) {             fprintf(stderr, "jq: --indent takes one parameter\n");             die();           }-          dumpopts &= ~(JV_PRINT_TAB | JV_PRINT_INDENT_FLAGS(7));-          int indent = atoi(argv[i+1]);-          if (indent < -1 || indent > 7) {+          char* end = NULL;+          errno = 0;+          long indent = strtol(argv[i+1], &end, 10);+          if (errno || indent < -1 || indent > 7 ||+              isspace(*argv[i+1]) || end == argv[i+1] || *end) {             fprintf(stderr, "jq: --indent takes a number between -1 and 7\n");             die();           }+          dumpopts &= ~(JV_PRINT_TAB | JV_PRINT_INDENT_FLAGS(7));           dumpopts |= JV_PRINT_INDENT_FLAGS(indent);           i++;         } else if (isoption(&text, 0, "seq", is_short)) {

tests/jq.test

@@ -2342,8 +2342,8 @@ null 2  .[] |= try tonumber-["1", "2a", "3", " 4 ", "5.67", ".89", "-876", "+5.43", 21]-[1, 3, 5.67, 0.89, -876, 5.43, 21]+["1", "2a", "3", " 4", "5 ", "6.7", ".89", "-876", "+5.43", 21]+[1, 3, 6.7, 0.89, -876, 5.43, 21]  # Also 1859, but from 2073 any(keys[]|tostring?;true)