1. PHP / Говнокод #18191

    +144

    1. 1
    2. 2
    3. 3
    4. 4
    /* Convert IP address to unsigned long int. */
    function ip2ulong($ip) {
        return sprintf("%u", ip2long32($ip));
    }

    https://github.com/pfsense/pfsense/blob/master/etc/inc/util.inc#L414-L417

    Продолжаем осторожно идти дальше. Это нормально вообще? Да, я видел всякие смехуёчки с магическим приведением типов в PHP и JS, так что наверняка оно где-то правильно конвертится и все работает несмотря ни на что и вопреки. Может, это даже нормальная практика в мире PHP, но мне страшно.

    Запостил: superhacker777, 17 Мая 2015

    Комментарии (19) RSS

    • Я выяснил кое-что. В мире PHP есть только знаковый целочисленный тип длиной в аж 32 бита, лонгов нет. Байтоебле это дело не мешает, но ребятам нужно проверять, чтобы начало блока IP-адресов было меньше конца (ну и не только). Знак тут может сильно делу помешать. Поэтому они делают это так:
      /* Return true if the first IP is 'after' the second */
      function ip_greater_than($ip1, $ip2) {
        // Compare as unsigned long because otherwise it wouldn't work
        //   when crossing over from 127.255.255.255 / 128.0.0.0 barrier
        return ip2ulong($ip1) > ip2ulong($ip2);
      }


      Ну, сравнение строк-то работает как надо. Во-от.
      Ответить
      • > как надо
        Потому что большие числа внутри строк кастуются в дабл?
        Ответить
        • Нет, обычное сравнение строк. «Стол» меньше «табуретки», так как у строки длина меньше, но «стол» больше, чем «Стол», так как сначала идут в кодовых таблицах заглавные, а значит номера у них поменьше.
          Ответить
          • Но ведь "стул" > "столик", хотя длина строки у столика больше. Почему же для чисел работает?
            Ответить
            • O-o-oh sh~ Тогда, очевидно, длина не учитывается. Вернее, сравнивают по самому короткому. Страшно представить, к чему приведет такая хуйня.
              Ответить
              • Тогда это надо сделать как-то так. Добавить только еще проверку на размер инта.

                function is_greater_than($left, $right) {
                  if ($left < 0) {
                    if ($right >= 0)
                      return true;
                    else
                      return $left > $right;
                  } else {
                    if ($right < 0)
                      return false;
                    else
                      return $left > $right;
                  }
                }


                Все правильно ведь сделал?
                Ответить
              • Ниже я привёл выдержку из исходников самого PHP. Его ядро при сравнении строк пытается сначала выделить из каждой строки числовое значение типа дабл и типа лонг, после чего сравнивает их как даблы или как лонги (именно как лонги, хотя для числовых операций long в PHP нигде не используется) и лишь в случае, когда числа из строк выделить нельзя, сравнивает их как обычные строки (т. е. по самому короткому аргументу).
                Ответить
              • Впрочем, хуета. Как видно ниже, там попытка каста в дабл и все должно работать как ожидается. И хотя условия погонять будет быстрее, чем строку форматировать, разницы большой нет.

                Сам отвечу на свой вопрос: да, это нормальная практика.
                Ответить
      • Всё намного сложнее. Есть два варианта интерпретатора PHP: с 32-битным целочисленным типом и с 64-битным целочисленным типом. Для 32-битной ОС есть только PHP с 32-битным целым.

        Странно при этом, что сравнение больших чисел, приведённых к строкам в десятичной системе счисления, работает.
        Ответить
        • Ну я сверху описал, как сравнение строк по-идее должно работать. Я себе это всегда так представлял, но, честно, не удивлюсь, если попытки кастов где-нибудь есть в PHP. Про размеры интов знаю. Там, кажется, конфиг править надо на 64-битной ОС, чтобы он подлиннее использовал числа.
          Ответить
          • А я кое-что нашёл:
            ZEND_API void zendi_smart_strcmp(zval *result, zval *s1, zval *s2) /* {{{ */
            {
            	int ret1, ret2;
            	long lval1, lval2;
            	double dval1, dval2;
            
            	if ((ret1=is_numeric_string(Z_STRVAL_P(s1), Z_STRLEN_P(s1), &lval1, &dval1, 0)) &&
            		(ret2=is_numeric_string(Z_STRVAL_P(s2), Z_STRLEN_P(s2), &lval2, &dval2, 0))) {
            		if ((ret1==IS_DOUBLE) || (ret2==IS_DOUBLE)) {
            			if (ret1!=IS_DOUBLE) {
            				dval1 = (double) lval1;
            			} else if (ret2!=IS_DOUBLE) {
            				dval2 = (double) lval2;
            			} else if (dval1 == dval2 && !zend_finite(dval1)) {
            				/* Both values overflowed and have the same sign,
            				 * so a numeric comparison would be inaccurate */
            				goto string_cmp;
            			}
            			Z_DVAL_P(result) = dval1 - dval2;
            			ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
            		} else { /* they both have to be long's */
            			ZVAL_LONG(result, lval1 > lval2 ? 1 : (lval1 < lval2 ? -1 : 0));
            		}
            	} else {
            string_cmp:
            		Z_LVAL_P(result) = zend_binary_zval_strcmp(s1, s2);
            		ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(result)));
            	}
            }

            PHP 5.3.
            Ответить
            • А что делает is_numeric_string с большими числами, которые не входят в лонг, типа "10000000000"? Всё-таки парсит как дабл?
              Ответить
              • Парсит как лонг. Если натыкается на точку или экспоненту, то парсит как дабл. Если длина выходит больше MAX_LENGTH_OF_LONG, то false.
                Ответить
              • Там код на >120 строк. Сейчас разберу.

                1. Сначала он ищет 0x или 0X и в случае успеха интерпретирует строку как шестнадцатеричное число.

                2. Если длина шестнадцатеричной строки >= SIZEOF_LONG * 2, парсит её как шестнадцатеричную, но приводит к даблу.

                3. Потом он пытается найти точку или e или E. Если находит, интерпретирует строку как дабл.

                5. Сравнивает число знаков с MAX_LENGTH_OF_LONG. Если больше, то тоже интерпретирует как дабл.

                6. Потом делает ещё непонятное сравнение для случая, когда число знаков точно равно MAX_LENGTH_OF_LONG - 1.

                7. Если все тесты провалены, то парсит строку как лонг.
                Ответить
                • P.S. Для интересующихся, кто все эти константы:
                  #if SIZEOF_LONG == 4
                  #define MAX_LENGTH_OF_LONG 11
                  static const char long_min_digits[] = "2147483648";
                  #elif SIZEOF_LONG == 8
                  #define MAX_LENGTH_OF_LONG 20
                  static const char long_min_digits[] = "9223372036854775808";
                  #else
                  #error "Unknown SIZEOF_LONG"
                  #endif
                  Ответить
      • > Ну, сравнение строк-то работает как надо. Во-от.
        А если 0.0.7.0 и 0.0.69.0 (1792 и 17664, если я правильно посчитал).
        Ответить
        • А не, нашёл коммент inkanus-gray про каст в число. Тогда да, прокатит)
          Используем всю магию языка по максимому)))
          Ответить
          • Оказывается, и long в PHP есть, но чтобы его откопать, нужно число перевести в строку. Поле чудес...
            Ответить
            • Это лонг в С++, я правильно понимаю? И они его для своих темных дел используют, но интерпретатор понятия о нем не имеет, получается.
              Ответить

    Добавить комментарий