Check overflow in expr(1).

This commit is contained in:
Jonas 'Sortie' Termansen 2016-04-27 20:25:03 +02:00
parent 2a6a246ff4
commit e4a3bb997b
1 changed files with 98 additions and 75 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014 Jonas 'Sortie' Termansen. * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -17,8 +17,9 @@
* Evaluate expressions. * Evaluate expressions.
*/ */
#include <ctype.h>
#include <err.h>
#include <errno.h> #include <errno.h>
#include <error.h>
#include <inttypes.h> #include <inttypes.h>
#include <locale.h> #include <locale.h>
#include <regex.h> #include <regex.h>
@ -34,58 +35,71 @@
// `expr + 5 + + 2 +' // `expr + 5 + + 2 +'
// TODO: Support the other GNU function extensions documented in help(). // TODO: Support the other GNU function extensions documented in help().
char* strdup_or_die(const char* str) static bool integer_of_string(const char* str, intmax_t* out)
{
if ( !isdigit((unsigned char) str[0]) )
return false;
char* endptr;
errno = 0;
intmax_t result = strtoimax((char*) str, &endptr, 10);
if ( (result == INTMAX_MIN || result == INTMAX_MAX) && errno == ERANGE )
return false;
if ( *endptr )
return false;
return *out = result, true;
}
static char* strdup_or_die(const char* str)
{ {
char* result = strdup(str); char* result = strdup(str);
if ( !str ) if ( !str )
error(2, errno, "strdup"); err(2, "strdup");
return result; return result;
} }
char* strndup_or_die(const char* str, size_t n) static char* strndup_or_die(const char* str, size_t n)
{ {
char* result = strndup(str, n); char* result = strndup(str, n);
if ( !str ) if ( !str )
error(2, errno, "strndup"); err(2, "strndup");
return result; return result;
} }
char* print_intmax_or_die(intmax_t value) static char* print_intmax_or_die(intmax_t value)
{ {
char value_string[sizeof(intmax_t) * 3]; char value_string[sizeof(intmax_t) * 3];
snprintf(value_string, sizeof(value_string), "%ji", value); snprintf(value_string, sizeof(value_string), "%ji", value);
return strdup_or_die(value_string); return strdup_or_die(value_string);
} }
__attribute__((noreturn)) static void syntax_error(void)
void syntax_error(void)
{ {
error(2, 0, "syntax error"); errx(2, "syntax error");
__builtin_unreachable();
} }
__attribute__((noreturn)) static void non_integer_argument(void)
void non_integer_argument(void)
{ {
error(2, 0, "non-integer argument"); errx(2, "non-integer argument");
__builtin_unreachable();
} }
__attribute__((noreturn)) static void division_by_zero(void)
void division_by_zero(void)
{ {
error(2, 0, "division by zero"); errx(2, "division by zero");
__builtin_unreachable();
} }
char* interpret(char** tokens, size_t num_tokens); static void overflow(const char* op)
{
errx(2, "%s overflow", op);
}
char* interpret_left_associative(char** tokens, static char* interpret(char** tokens, size_t num_tokens);
size_t num_tokens,
const char* operator_name, static char* interpret_left_associative(char** tokens,
char* (*next)(char**, size_t, const void*), size_t num_tokens,
const void* next_context, const char* operator_name,
char* (*function)(const char*, const char*)) char* (*next)(char**, size_t, const void*),
const void* next_context,
char* (*function)(const char*, const char*))
{ {
size_t depth = 0; size_t depth = 0;
for ( size_t n = num_tokens; n != 0; n-- ) for ( size_t n = num_tokens; n != 0; n-- )
@ -132,14 +146,14 @@ char* interpret_left_associative(char** tokens,
return next(tokens, num_tokens, next_context); return next(tokens, num_tokens, next_context);
} }
char* bool_to_boolean_value(bool b) static char* bool_to_boolean_value(bool b)
{ {
return strdup_or_die(b ? "1" : "0"); return strdup_or_die(b ? "1" : "0");
} }
char* interpret_literal(char** tokens, static char* interpret_literal(char** tokens,
size_t num_tokens, size_t num_tokens,
const void* ctx) const void* ctx)
{ {
(void) ctx; (void) ctx;
if ( num_tokens != 1 ) if ( num_tokens != 1 )
@ -147,9 +161,9 @@ char* interpret_literal(char** tokens,
return strdup_or_die(tokens[0]); return strdup_or_die(tokens[0]);
} }
char* interpret_parentheses(char** tokens, static char* interpret_parentheses(char** tokens,
size_t num_tokens, size_t num_tokens,
const void* ctx) const void* ctx)
{ {
if ( 2 <= num_tokens && if ( 2 <= num_tokens &&
strcmp(tokens[0], "(") == 0 && strcmp(tokens[0], "(") == 0 &&
@ -158,7 +172,7 @@ char* interpret_parentheses(char** tokens,
return interpret_literal(tokens, num_tokens, ctx); return interpret_literal(tokens, num_tokens, ctx);
} }
char* evaluate_and(const char* a, const char* b) static char* evaluate_and(const char* a, const char* b)
{ {
if ( strcmp(a, "") != 0 && strcmp(a, "0") != 0 && if ( strcmp(a, "") != 0 && strcmp(a, "0") != 0 &&
strcmp(b, "") != 0 && strcmp(b, "0") != 0 ) strcmp(b, "") != 0 && strcmp(b, "0") != 0 )
@ -166,7 +180,7 @@ char* evaluate_and(const char* a, const char* b)
return strdup_or_die("0"); return strdup_or_die("0");
} }
char* evaluate_or(const char* a, const char* b) static char* evaluate_or(const char* a, const char* b)
{ {
if ( strcmp(a, "") != 0 && strcmp(a, "0") != 0 ) if ( strcmp(a, "") != 0 && strcmp(a, "0") != 0 )
return strdup_or_die(a); return strdup_or_die(a);
@ -175,14 +189,11 @@ char* evaluate_or(const char* a, const char* b)
return strdup_or_die("0"); return strdup_or_die("0");
} }
int compare_values(const char* a, const char* b) static int compare_values(const char* a, const char* b)
{ {
// TODO: Compute using arbitrary length integers. intmax_t a_int, b_int;
char* a_endptr; if ( integer_of_string(a, &a_int) &&
char* b_endptr; integer_of_string(b, &b_int) )
intmax_t a_int = strtoimax((char*) a, &a_endptr, 10);
intmax_t b_int = strtoimax((char*) b, &b_endptr, 10);
if ( a[0] && !*a_endptr && b[0] && !*b_endptr )
{ {
if ( a_int < b_int ) if ( a_int < b_int )
return -1; return -1;
@ -193,104 +204,116 @@ int compare_values(const char* a, const char* b)
return strcoll(a, b); return strcoll(a, b);
} }
char* evaluate_eq(const char* a, const char* b) static char* evaluate_eq(const char* a, const char* b)
{ {
return bool_to_boolean_value(compare_values(a, b) == 0); return bool_to_boolean_value(compare_values(a, b) == 0);
} }
char* evaluate_gt(const char* a, const char* b) static char* evaluate_gt(const char* a, const char* b)
{ {
return bool_to_boolean_value(0 < compare_values(a, b)); return bool_to_boolean_value(0 < compare_values(a, b));
} }
char* evaluate_ge(const char* a, const char* b) static char* evaluate_ge(const char* a, const char* b)
{ {
return bool_to_boolean_value(0 <= compare_values(a, b)); return bool_to_boolean_value(0 <= compare_values(a, b));
} }
char* evaluate_lt(const char* a, const char* b) static char* evaluate_lt(const char* a, const char* b)
{ {
return bool_to_boolean_value(compare_values(a, b) < 0); return bool_to_boolean_value(compare_values(a, b) < 0);
} }
char* evaluate_le(const char* a, const char* b) static char* evaluate_le(const char* a, const char* b)
{ {
return bool_to_boolean_value(compare_values(a, b) <= 0); return bool_to_boolean_value(compare_values(a, b) <= 0);
} }
char* evaluate_neq(const char* a, const char* b) static char* evaluate_neq(const char* a, const char* b)
{ {
return bool_to_boolean_value(compare_values(a, b) != 0); return bool_to_boolean_value(compare_values(a, b) != 0);
} }
char* evaluate_integer_function(const char* a, const char* b, static char* evaluate_integer_function(const char* a, const char* b,
intmax_t (*function)(intmax_t, intmax_t)) intmax_t (*function)(intmax_t, intmax_t))
{ {
// TODO: Compute using arbitrary length integers. intmax_t a_int;
char* a_endptr; intmax_t b_int;
char* b_endptr; if ( !integer_of_string(a, &a_int) ||
intmax_t a_int = strtoimax((char*) a, &a_endptr, 10); !integer_of_string(b, &b_int) )
intmax_t b_int = strtoimax((char*) b, &b_endptr, 10);
if ( !a[0] || *a_endptr || !b[0] || *b_endptr )
non_integer_argument(); non_integer_argument();
return print_intmax_or_die(function(a_int, b_int)); return print_intmax_or_die(function(a_int, b_int));
} }
intmax_t integer_add(intmax_t a, intmax_t b) static intmax_t integer_add(intmax_t a, intmax_t b)
{ {
return a + b; intmax_t result = 0;
if ( __builtin_add_overflow(a, b, &result) )
overflow("addition");
return result;
} }
char* evaluate_add(const char* a, const char* b) static char* evaluate_add(const char* a, const char* b)
{ {
return evaluate_integer_function(a, b, integer_add); return evaluate_integer_function(a, b, integer_add);
} }
intmax_t integer_sub(intmax_t a, intmax_t b) static intmax_t integer_sub(intmax_t a, intmax_t b)
{ {
return a - b; intmax_t result = 0;
if ( __builtin_sub_overflow(a, b, &result) )
overflow("subtraction");
return result;
} }
char* evaluate_sub(const char* a, const char* b) static char* evaluate_sub(const char* a, const char* b)
{ {
return evaluate_integer_function(a, b, integer_sub); return evaluate_integer_function(a, b, integer_sub);
} }
intmax_t integer_mul(intmax_t a, intmax_t b) static intmax_t integer_mul(intmax_t a, intmax_t b)
{ {
return a * b; intmax_t result = 0;
if ( __builtin_mul_overflow(a, b, &result) )
overflow("multiplication");
return result;
} }
char* evaluate_mul(const char* a, const char* b) static char* evaluate_mul(const char* a, const char* b)
{ {
return evaluate_integer_function(a, b, integer_mul); return evaluate_integer_function(a, b, integer_mul);
} }
intmax_t integer_div(intmax_t a, intmax_t b) static intmax_t integer_div(intmax_t a, intmax_t b)
{ {
if ( b == 0 ) if ( b == 0 )
division_by_zero(); division_by_zero();
if ( a == INTMAX_MIN && b == -1 )
overflow("division");
return a / b; return a / b;
} }
char* evaluate_div(const char* a, const char* b) static char* evaluate_div(const char* a, const char* b)
{ {
return evaluate_integer_function(a, b, integer_div); return evaluate_integer_function(a, b, integer_div);
} }
intmax_t integer_mod(intmax_t a, intmax_t b) // TODO: Is this fully well-defined?
static intmax_t integer_mod(intmax_t a, intmax_t b)
{ {
if ( b == 0 ) if ( b == 0 )
division_by_zero(); division_by_zero();
if ( a == INTMAX_MIN && b == -1 )
overflow("division");
return a % b; return a % b;
} }
char* evaluate_mod(const char* a, const char* b) static char* evaluate_mod(const char* a, const char* b)
{ {
return evaluate_integer_function(a, b, integer_mod); return evaluate_integer_function(a, b, integer_mod);
} }
char* evaluate_match(const char* a, const char* b) static char* evaluate_match(const char* a, const char* b)
{ {
regex_t regex; regex_t regex;
int status = regcomp(&regex, b, 0); int status = regcomp(&regex, b, 0);
@ -309,7 +332,7 @@ char* evaluate_match(const char* a, const char* b)
regerror(status, &regex, erralloc, errbuf_needed); regerror(status, &regex, erralloc, errbuf_needed);
} }
} }
error(2, 0, "compiling regular expression: %s", errmsg); errx(2, "compiling regular expression: %s", errmsg);
free(erralloc); free(erralloc);
} }
@ -360,9 +383,9 @@ struct binary_operator binary_operators[] =
{ ":", evaluate_match }, { ":", evaluate_match },
}; };
char* interpret_binary_operator(char** tokens, static char* interpret_binary_operator(char** tokens,
size_t num_tokens, size_t num_tokens,
const void* context) const void* context)
{ {
size_t index = *(const size_t*) context; size_t index = *(const size_t*) context;
size_t next_index = index + 1; size_t next_index = index + 1;
@ -386,7 +409,7 @@ char* interpret_binary_operator(char** tokens,
next, next_context, binop->function); next, next_context, binop->function);
} }
char* interpret(char** tokens, size_t num_tokens) static char* interpret(char** tokens, size_t num_tokens)
{ {
if ( !num_tokens ) if ( !num_tokens )
syntax_error(); syntax_error();