Почему Tcler предлагает подтянуть ваши `expr`essions?

Мы можем оценить два выражения двумя возможными способами:

   set a 1
   set b 1
   puts [expr $a + $b ]
   puts [expr {$a + $b } ]

Но почему ненавидеть опытных Тклерсов первый, и считать это плохой практикой? Имеет ли первое использование expr некоторые проблемы безопасности?

Ответ 1

"Проблема" с expr заключается в том, что она реализует свой собственный "мини-язык", который включает, среди прочего, замену переменных (заменяя те $a -s своими значениями) и замену команд (заменяя те [command ...] вещи с результатами работы command s), поэтому в основном процесс оценки expr $a + $b выглядит следующим образом:

  • Интерпретатор Tcl анализирует четыре слова -— expr, $a, + и $b из исходной строки. Поскольку два из этих слов начинаются с $, замена переменных происходит так, что на самом деле будут expr, 1, + и 2.
  • Как обычно, первое слово считается именем команды, а другие являются аргументом для него, поэтому интерпретатор Tcl ищет команду с именем expr и выполняет ее, передавая ей три аргумента: 1, + и 2.
  • Реализация if expr затем объединяет все переданные ей аргументы, интерпретируя их как строки, получая строку 1 + 2.
  • Затем эта строка снова обрабатывается — на этот раз механизмом expr, согласно его собственным правилам, которые включают подстановки переменных и команд, как уже упоминалось.

Что следует:

  • Если вы привязываете свои expr эссенции, как в expr {$a + $b}, группировка, предоставляемая этими фигурными фигурными скобками, запрещает интерпретацию интерпретатором Tcl 1 script, который должен быть проанализирован самим expr. Это означает, что в нашем игрушечном примере команда expr увидит ровно один аргумент $a + $b и сама выполнит подстановки.
  • "Двойной парсинг", описанный выше, может привести к проблемам безопасности.

    Например, в следующем коде

    set a {[exec echo rm -rf $::env(HOME)]}
    set b 2
    expr $a + $b
    

    Команда expr сама проанализирует строку [exec echo rm -rf $::env(HOME)] + 2. Его оценка потерпит неудачу, но к тому времени содержимое вашего домашнего каталога, предположительно, исчезнет. (Обратите внимание, что вид Tcler помещен echo перед rm в более позднем редактировании моего ответа, пытаясь сохранить шейки случайных копий, поэтому команда в письменном виде не вызовет rm, но если вы удалите echo от него, будет.)

  • Двойной синтаксический анализ запрещает определенные оптимизации, которые может использовать механизм Tcl при работе с вызовами expr.

1 Ну, почти; последовательности "backslash + newline" все еще обрабатываются даже внутри {...} блоков.

Ответ 2

У него наверняка есть проблемы с безопасностью. В частности, он будет рассматривать содержимое переменных как фрагменты выражения, а не значения, и это позволяет встретить всевозможные проблемы. Если этого недостаточно, те же проблемы также полностью убьют производительность, потому что нет возможности создать для него разумно оптимальный код: генерируемый байт-код будет намного менее эффективным, поскольку все, что он может сделать, это собрать строку выражения и отправить ее для второго раунда разбора.

Перейдем к деталям

% tcl::unsupported::disassemble lambda {{} {
    set a 1; set b 2
    puts [expr {$a + $b}]
    puts [expr $a + $b]
}}
ByteCode 0x0x50910, refCt 1, epoch 3, interp 0x0x31c10 (epoch 3)
  Source "\n    set a 1; set b 2\n    puts [expr {$a + $b}]\n    put"
  Cmds 6, src 72, inst 65, litObjs 5, aux 0, stkDepth 6, code/src 0.00
  Proc 0x0x6d750, refCt 1, args 0, compiled locals 2
      slot 0, scalar, "a"
      slot 1, scalar, "b"
  Commands 6:
      1: pc 0-4, src 5-11          2: pc 5-18, src 14-20
      3: pc 19-37, src 26-46       4: pc 21-34, src 32-45
      5: pc 38-63, src 52-70       6: pc 40-61, src 58-69
  Command 1: "set a 1"
    (0) push1 0     # "1"
    (2) storeScalar1 %v0    # var "a"
    (4) pop 
  Command 2: "set b 2"
    (5) startCommand +13 1  # next cmd at pc 18
    (14) push1 1    # "2"
    (16) storeScalar1 %v1   # var "b"
    (18) pop 
  Command 3: "puts [expr {$a + $b}]"
    (19) push1 2    # "puts"
  Command 4: "expr {$a + $b}"
    (21) startCommand +14 1     # next cmd at pc 35
    (30) loadScalar1 %v0    # var "a"
    (32) loadScalar1 %v1    # var "b"
    (34) add 
    (35) invokeStk1 2 
    (37) pop 
  Command 5: "puts [expr $a + $b]"
    (38) push1 2    # "puts"
  Command 6: "expr $a + $b"
    (40) startCommand +22 1     # next cmd at pc 62
    (49) loadScalar1 %v0    # var "a"
    (51) push1 3    # " "
    (53) push1 4    # "+"
    (55) push1 3    # " "
    (57) loadScalar1 %v1    # var "b"
    (59) concat1 5 
    (61) exprStk 
    (62) invokeStk1 2 
    (64) done 

В частности, посмотрите адреса 30-34 (компиляция expr {$a + $b}) и сравните с адресами 49-61 (компиляция expr $a + $b). Оптимальный код считывает значения из двух переменных и только add их; необработанный код должен читать переменные и конкатенировать с буквальными частями выражения, а затем запускает результат в exprStk, который является "вычислением строки выражения". (Относительное количество байт-кодов не проблема, проблема - оценка времени выполнения.)

Для того, насколько фундаментальными могут быть эти различия, рассмотрите настройку a на 1 || 0 и b на [exit 1]. В случае предварительно скомпилированной версии Tcl будет просто пытаться обрабатывать обе стороны как числа для добавления (ни один из них на самом деле не является числовым, вы получите сообщение об ошибке). В случае с динамической версией... ну, можете ли вы предсказать ее путем проверки?

Итак, что вы делаете?

Оптимальный код Tcl должен всегда ограничивать объем оценки выполнения выражений, которые он выполняет; вы обычно можете вообще ничего не делать, если только вы не делаете то, что принимает выражение, определенное пользователем или что-то в этом роде. Если у вас есть это, попробуйте создать единую строку выражения в переменной, а затем просто используйте expr $thatVar, а не что-нибудь более сложное. Если вы хотите добавить список чисел (или, как правило, применяя любой оператор для их объединения), подумайте об этом:

set sum [tcl::mathop::+ {*}$theList]

вместо:

set sum [expr [join $theList "+"]]

(Кроме того, никогда не используйте динамическое выражение с if, for или while, поскольку это будет подавлять много компиляции.)

Помните, что с Tcl это (обычно) случай, когда безопасный код является быстрым кодом. Вы хотите быстрый и безопасный код, верно?

Ответ 3

  • Без фигурных скобок параметры expr сначала преобразуются в строку, а затем снова возвращаются к номерам.
  • Без брекетов они подвержены инъекционным атакам, очень похожим на атаки SQL-инъекций.
  • Вы можете получить ошибки округления, которые вам не нужны, если вы не используете фигурные скобки.
  • С помощью фигурных скобок выражения могут быть скомпилированы.

Я основывался на этом на Johannes Kuhn answer, который был опубликован некоторое время назад, и вы можете узнать в цифрах, как усиленные функции более эффективны на wiki, а также другие интересные вещи о различиях и где вы можете опустить фигурные скобки, чтобы получить нужные результаты.