Не удается получить strtol () для обнаружения переполнения - PullRequest
0 голосов
/ 25 июня 2019

Программа использует лексический сканер для классификации токенов как символа, строки, десятичного числа, шестнадцатеричного числа и т. Д. При обнаружении «числа» оно передается в strtol() для преобразования его во внутреннее 32-разрядное двоичное значение. Однако я не могу заставить strtol() надежно вернуть ошибку при переполнении.

Часть кода конверсии:

    errno = 0;      // erase any previous error in errno
    switch (constType) {
…
case lxHex:         // hexadecimal number X'1234567890ABCDEF' (X-string)
fprintf(stderr,"** FindConstantFromString - converting %s\n",constBuffer);
        newDictEntry->dcValue = strtol(constBuffer+2, NULL, 16);
int myerr = errno;
fprintf(stderr,"     value %x errno %d\n",newDictEntry->dcValue, myerr);
        newDictEntry->dcType = syNumber;
        newDictEntry->dcSubType = 4;    // hexadecimal
        if  (   EINVAL == errno
            ||  ERANGE == errno
            ) {
            ErrDict = newDictEntry;
            AnaError (ConstMsg+2);
            newDictEntry->dcType = sySLit;
        };
        result.cstClass = newDictEntry->dcType;
        return result;
…

Когда этот код проверяется с неправильным вводом, он обнаруживает переполнение, только если первая шестнадцатеричная цифра> = 8 (потенциально дает отрицательное значение), как продемонстрировано:

    29            | declare v;
    30            | v = x'fedcba9876543210'
** FindConstantFromString - meeting x'fedcba9876543210' as 11
** FindConstantFromString - converting x'fedcba9876543210'
     value ffffffff errno 34
*Error 32: Candidate number x'fedcba9876543210' too large or could not be converted
*Error 20: Unrecognisable lexical unit x'fedcba9876543210' at 30.5
    31            |     + x'123456789abcdef'
** FindConstantFromString - meeting x'123456789abcdef' as 11
** FindConstantFromString - converting x'123456789abcdef'
     value 89abcdef errno 0
    32            |     + 9876543210
** FindConstantFromString - meeting x'fedcba9876543210' as 8
                            symbol already known
** FindConstantFromString - converting x'fedcba9876543210'
     value 0 errno 0
*Error 32: Candidate number x'fedcba9876543210' too large or could not be converted
** FindConstantFromString - meeting 9876543210 as 8
** FindConstantFromString - converting 9876543210
     value 4cb016ea errno 0
    33            |     + '12345a'
** FindConstantFromString - meeting 12345a as 3
    34            |     + '';
** FindConstantFromString - meeting 12345a as 8
                            symbol already known
** FindConstantFromString - converting 12345a
     value 3039 errno 0
*Error 32: Candidate number 12345a too large or could not be converted
** FindConstantFromString - meeting  as 3
** FindConstantFromString - meeting  as 8
                            symbol already known
*Error 33: Empty string cannot be converted to number

В строке 30 лексический сканер распознал шестнадцатеричное число и запросил преобразование из этой шестнадцатеричной формы (11 = lxHex). strtol() правильно устанавливает errno на ERANGE и выдается сообщение об ошибке. Переполненное шестнадцатеричное число затем сохраняется в словаре в виде строки.

Обратите внимание, что возвращаемое значение равно -1, а не LONG_MAX.

В строке 31 у нас снова есть другое переполняющееся шестнадцатеричное число, но оно не начинается с 8-9a-f. Он снова определяется как шестнадцатеричное число. Попытка преобразования предпринята, но значение errno не установлено вообще. Значение соответствует младшим 32 битам числа. Поскольку это считается успешным, усеченное значение сохраняется как результат.

Когда + применяется к «x'fed…» и 89abcdef, предпринимается попытка другого преобразования для строки «x'fed…», которая должна быть десятичным числом (обозначается как 8-запрос) и преобразование завершается неудачно, потому что «x» не может начинать десятичное число.

В строке 32 имеется десятичное число переполнения 987654321. Еще раз, переполнение не обнаружено (код не показан, но похож на тот, что используется для шестнадцатеричных чисел с добавлением теста для "endptr", поскольку строки не могут быть отфильтрованы лексическим сканером и содержат недопустимые символы). Возвращаемое значение содержит как минимум 32 бита числа.

Если я изменю strtol() на strtoul(), первая ошибка ERANGE исчезнет, ​​и я получу как минимум 32 бита числа.

Что я делаю не так?

Система: Fedora Linux 29 glibc: 2,27

1 Ответ

1 голос
/ 26 июня 2019

strtol() выводит значение long (т.е. signed long) из заданной строки.Он заботится только о переполнении или недостаточном потоке 64-битного значения long, которое он создает внутри, и что он в конечном итоге вернется к своему вызывающему, если не возникнет проблем.Он не заботится о переполнении или потере 32-битных значений.

Именно поэтому strtol() возвращает ошибку только в тех примерах, которые вы показываете, когда значение long было бы переполнено в отрицательное значение.-битный номер.(И, как вы заметили, strtoul() не жалуется в этом случае, потому что в этом случае переполнение значения unsigned long не происходит. Вам нужно будет ввести strtoul() 17-значную строку, чтобы переполнить unsigned long.)

strtol() также не знает и не заботится о том, что ваша программа получит 64-битный результат long и немедленно отбросит свои старшие 4 байта, присвоив значение 32-битной переменной.Именно из-за этого усечения вы склонялись к мысли, что « возвращает значение -1, а не LONG_MAX».Фактически результат от strtol() был LONG_MAX, но ваша программа отбросила 4 старших байта LONG_MAX и осталась только с 4 младшими байтами, значение которых 0xffffffff или -1, когда рассматривается как 32-битный int.

Если вы хотите использовать strtol() для генерации и проверки 32-битных значений, вам придется самостоятельно выполнять дополнительную проверку диапазона.После первого сбора результата strtol() в переменную long и проверки, указывает ли этот результат 64-битное переполнение или недостаточное значение во время выполнения strtol(), вы можете сравнить этот результат long с INT_MAX и INT_MIN, чтобы увидеть, будет ли его значение переполняться или уменьшаться 32-битной переменной.

Очевидно, вы можете обернуть это в небольшую функцию, которая может (если вы сделаете соответствующую манипуляцию с errno) вести себя простокак strtol() за исключением того, что оно применяется к int значениям, а не long.Тем не менее, вы должны отказаться от стремления дать вашей функции имя strtoi, поскольку имена, начинающиеся с str[a-z], зарезервированы POSIX для будущего использования в стандартной библиотеке.Некоторые системы могут уже иметь strtoi, а когда-нибудь Linux может его получить.

...