1
0
mirror of https://github.com/stedolan/jq.git synced 2024-05-11 05:55:39 +00:00

Fix #814: raise on div-0, add inf isinf nan isnan

This commit is contained in:
Nicolas Williams
2015-06-17 19:46:57 -05:00
parent bdc1feb50e
commit b9c2a326ba
8 changed files with 477 additions and 388 deletions

View File

@@ -216,6 +216,8 @@ static jv f_multiply(jq_state *jq, jv input, jv a, jv b) {
static jv f_divide(jq_state *jq, jv input, jv a, jv b) { static jv f_divide(jq_state *jq, jv input, jv a, jv b) {
jv_free(input); jv_free(input);
if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) { if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
if (jv_number_value(b) == 0.0)
return type_error2(a, b, "cannot be divided because the divisor is zero");
return jv_number(jv_number_value(a) / jv_number_value(b)); return jv_number(jv_number_value(a) / jv_number_value(b));
} else if (jv_get_kind(a) == JV_KIND_STRING && jv_get_kind(b) == JV_KIND_STRING) { } else if (jv_get_kind(a) == JV_KIND_STRING && jv_get_kind(b) == JV_KIND_STRING) {
return jv_string_split(a, b); return jv_string_split(a, b);
@@ -227,12 +229,11 @@ static jv f_divide(jq_state *jq, jv input, jv a, jv b) {
static jv f_mod(jq_state *jq, jv input, jv a, jv b) { static jv f_mod(jq_state *jq, jv input, jv a, jv b) {
jv_free(input); jv_free(input);
if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) { if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
if ((intmax_t)jv_number_value(b) == 0) { if ((intmax_t)jv_number_value(b) == 0)
return jv_invalid_with_msg(jv_string("Cannot mod by zero.")); return type_error2(a, b, "cannot be divided (remainder) because the divisor is zero");
}
return jv_number((intmax_t)jv_number_value(a) % (intmax_t)jv_number_value(b)); return jv_number((intmax_t)jv_number_value(a) % (intmax_t)jv_number_value(b));
} else { } else {
return type_error2(a, b, "cannot be divided"); return type_error2(a, b, "cannot be divided (remainder)");
} }
} }
@@ -831,6 +832,38 @@ static jv f_type(jq_state *jq, jv input) {
return out; return out;
} }
static jv f_isinf(jq_state *jq, jv input) {
jv_kind k = jv_get_kind(input);
if (k != JV_KIND_NUMBER) {
jv_free(input);
return jv_false();
}
double n = jv_number_value(input);
jv_free(input);
return isinf(n) ? jv_true() : jv_false();
}
static jv f_isnan(jq_state *jq, jv input) {
jv_kind k = jv_get_kind(input);
if (k != JV_KIND_NUMBER) {
jv_free(input);
return jv_false();
}
double n = jv_number_value(input);
jv_free(input);
return isnan(n) ? jv_true() : jv_false();
}
static jv f_inf(jq_state *jq, jv input) {
jv_free(input);
return jv_number(INFINITY);
}
static jv f_nan(jq_state *jq, jv input) {
jv_free(input);
return jv_number(NAN);
}
static jv f_error(jq_state *jq, jv input, jv msg) { static jv f_error(jq_state *jq, jv input, jv msg) {
jv_free(input); jv_free(input);
return jv_invalid_with_msg(msg); return jv_invalid_with_msg(msg);
@@ -1183,6 +1216,10 @@ static const struct cfunction function_list[] = {
{(cfunction_ptr)f_contains, "contains", 2}, {(cfunction_ptr)f_contains, "contains", 2},
{(cfunction_ptr)f_length, "length", 1}, {(cfunction_ptr)f_length, "length", 1},
{(cfunction_ptr)f_type, "type", 1}, {(cfunction_ptr)f_type, "type", 1},
{(cfunction_ptr)f_isinf, "isinf", 1},
{(cfunction_ptr)f_isnan, "isnan", 1},
{(cfunction_ptr)f_inf, "inf", 1},
{(cfunction_ptr)f_nan, "nan", 1},
{(cfunction_ptr)f_sort, "sort", 1}, {(cfunction_ptr)f_sort, "sort", 1},
{(cfunction_ptr)f_sort_by_impl, "_sort_by_impl", 2}, {(cfunction_ptr)f_sort_by_impl, "_sort_by_impl", 2},
{(cfunction_ptr)f_group_by_impl, "_group_by_impl", 2}, {(cfunction_ptr)f_group_by_impl, "_group_by_impl", 2},

View File

@@ -2,6 +2,7 @@
#define _GNU_SOURCE // for strdup #define _GNU_SOURCE // for strdup
#endif #endif
#include <assert.h> #include <assert.h>
#include <math.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include "compile.h" #include "compile.h"
@@ -161,6 +162,12 @@ int block_is_const(block b) {
return (block_is_single(b) && b.first->op == LOADK); return (block_is_single(b) && b.first->op == LOADK);
} }
int block_is_const_inf(block b) {
return (block_is_single(b) && b.first->op == LOADK &&
jv_get_kind(b.first->imm.constant) == JV_KIND_NUMBER &&
isinf(jv_number_value(b.first->imm.constant)));
}
jv_kind block_const_kind(block b) { jv_kind block_const_kind(block b) {
assert(block_is_const(b)); assert(block_is_const(b));
return jv_get_kind(b.first->imm.constant); return jv_get_kind(b.first->imm.constant);

View File

@@ -22,6 +22,7 @@ block gen_op_simple(opcode op);
block gen_const(jv constant); block gen_const(jv constant);
block gen_const_global(jv constant, const char *name); block gen_const_global(jv constant, const char *name);
int block_is_const(block b); int block_is_const(block b);
int block_is_const_inf(block b);
jv_kind block_const_kind(block b); jv_kind block_const_kind(block b);
jv block_const(block b); jv block_const(block b);
block gen_op_target(opcode op, block target); block gen_op_target(opcode op, block target);

View File

@@ -598,7 +598,7 @@ sections:
that string that many times. that string that many times.
Dividing a string by another splits the first using the second Dividing a string by another splits the first using the second
as separators. as separators. Division by zero raises an error.
Multiplying two objects will merge them recursively: this works Multiplying two objects will merge them recursively: this works
like addition but if both objects contain a value for the like addition but if both objects contain a value for the
@@ -615,6 +615,9 @@ sections:
- program: '{"k": {"a": 1, "b": 2}} * {"k": {"a": 0,"c": 3}}' - program: '{"k": {"a": 1, "b": 2}} * {"k": {"a": 0,"c": 3}}'
input: 'null' input: 'null'
output: ['{"k": {"a": 0, "b": 2, "c": 3}}'] output: ['{"k": {"a": 0, "b": 2, "c": 3}}']
- program: '.[] | (1 / .)?'
input: '[1,0,-1]'
output: ['1', '-1']
- title: "`length`" - title: "`length`"
@@ -1073,6 +1076,25 @@ sections:
input: '[0, false, [], {}, null, "hello"]' input: '[0, false, [], {}, null, "hello"]'
output: ['["number", "boolean", "array", "object", "null", "string"]'] output: ['["number", "boolean", "array", "object", "null", "string"]']
- title: "`inf`, `nan`, `isinf`, `isnan`"
body: |
Some arithmetic operations can yield infinities and "not a
number" (NaN) values. The `isinf` builtin returns `true` if
its input is infinite. The `isnan` builtin returns `true` if
its input is a NaN. The `inf` builtin returns a positive
infinite value. The `nan` builtin returns a NaN.
Note that division by zero raises an error.
examples:
- program: '.[] | (inf * .) < 0'
input: '[-1, 1]'
output: ['true', 'false']
- program: 'inf, nan | type'
input: 'null'
output: ['"number"']
- title: "`sort, sort_by(path_expression)`" - title: "`sort, sort_by(path_expression)`"
body: | body: |

765
parser.c
View File

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,7 @@
extern int yydebug; extern int yydebug;
#endif #endif
/* "%code requires" blocks. */ /* "%code requires" blocks. */
#line 10 "parser.y" /* yacc.c:1909 */ #line 11 "parser.y" /* yacc.c:1909 */
#include "locfile.h" #include "locfile.h"
struct lexer_param; struct lexer_param;
@@ -156,7 +156,7 @@ struct lexer_param;
typedef union YYSTYPE YYSTYPE; typedef union YYSTYPE YYSTYPE;
union YYSTYPE union YYSTYPE
{ {
#line 30 "parser.y" /* yacc.c:1909 */ #line 31 "parser.y" /* yacc.c:1909 */
jv literal; jv literal;
block blk; block blk;

View File

@@ -1,7 +1,8 @@
%{ %{
#include <assert.h>
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include "compile.h" #include "compile.h"
#include "jv_alloc.h" #include "jv_alloc.h"
#define YYMALLOC jv_mem_alloc #define YYMALLOC jv_mem_alloc
@@ -423,10 +424,14 @@ Exp "*=" Exp {
Exp '/' Exp { Exp '/' Exp {
$$ = gen_binop($1, $3, '/'); $$ = gen_binop($1, $3, '/');
if (block_is_const_inf($$))
FAIL(@$, "Division by zero?");
} | } |
Exp '%' Exp { Exp '%' Exp {
$$ = gen_binop($1, $3, '%'); $$ = gen_binop($1, $3, '%');
if (block_is_const_inf($$))
FAIL(@$, "Remainder by zero?");
} | } |
Exp "/=" Exp { Exp "/=" Exp {

View File

@@ -1236,3 +1236,15 @@ try join(",") catch .
null null
{"if":0,"and":1,"or":2,"then":3,"else":4,"elif":5,"end":6,"as":7,"def":8,"reduce":9,"foreach":10,"try":11,"catch":12,"label":13,"import":14,"module":15} {"if":0,"and":1,"or":2,"then":3,"else":4,"elif":5,"end":6,"as":7,"def":8,"reduce":9,"foreach":10,"try":11,"catch":12,"label":13,"import":14,"module":15}
try (1/.) catch .
0
"number (1) and number (0) cannot be divided because the divisor is zero"
try (1%.) catch .
0
"number (1) and number (0) cannot be divided (remainder) because the divisor is zero"
%%FAIL
1/0
jq: error: Division by zero? at <top-level>, line 1: