Динамическое вычисление формулы в JavaScript: eval() или не eval()

#javascript #formula #eval

#javascript #формула #eval

Вопрос:

Я работаю над веб-приложением, в котором администратор может определять и адаптировать формулы, которые необходимо будет оценивать на основе входных значений (чисел), предоставляемых конечными пользователями.

Для наглядности, вот сокращенный пример того, что я бы, кроме:

 const obj = {
    type: "External wall in contact with the ground",
    layer: {
        base: {
            type: "Reinforced concrete (reinforcement 5 vol.%)",
            thickness: 100,                         // <-- user value
            lambda: 2.3,                            // <-- user value
            _r: "{{thickness}}/{{lambda}}/1000",    // <-- admin defined
            r: 0
        },
        waterproofing: {
            type: "Bitumen sheets (single layer)",
            share: 1,           // <-- user value
            _r: "{{share}}",    // <-- admin defined
            r: 0,
        },
        insulation: {
            type: "XPS",
            thickness: 100,                                 // <-- user value
            share: 1,                                       // <-- user value
            lambda: 0.040,                                  // <-- user value
            _r: "{{thickness}}*{{share}}/{{lambda}}/1000",  // <-- admin defined
            r: 0
        }
    }
}

Object.entries(obj.layer).forEach(([key, object]) => {
    var formula = object._r
    Object.keys(object).forEach(k =>
        formula = formula.replace(`{{${k}}}`, object[k])
    )

    obj.layer[key].r = eval(formula)
})

console.log(obj)
 

Это _r формула, определенная администратором. Это значения, {{value}} предоставленные конечными пользователями.

Цикл проходит через obj.layer свойства, чтобы оценить формулу и сохранить ответ r .

Результатом будет этот объект:

 {
  type: 'External wall in contact with the ground',
  layer: {
    base: {
      type: 'Reinforced concrete (reinforcement 5 vol.%)',
      thickness: 100,
      lambda: 2.3,
      _r: '{{thickness}}/{{lambda}}/1000',
      r: 0.043478260869565216
    },
    waterproofing: {
      type: 'Bitumen sheets (single layer)',
      share: 1,
      _r: '{{share}}',
      r: 1
    },
    insulation: {
      type: 'XPS',
      thickness: 100,
      share: 1,
      lambda: 0.04,
      _r: '{{thickness}}*{{share}}/{{lambda}}/1000',
      r: 2.5
    }
  }
}
 

Давайте пропустим тот факт, что я не проверяю структуру объекта и не проверяю, доступны ли все значения.

Я знаю eval() , что это считается «опасным». Не очень хорошей альтернативой было бы Function() . Тем не менее, не идеально.

Пока я вижу 3 возможности:

  1. Только администратор может изменять формулы. Таким образом, риск выполнения вредоносного кода очень низок. Что мне нужно, так это проверить / очистить значения (что-то вроде isFloat() ), и на этом все.
  2. Используя mathjs библиотеку, которая предлагает приятную evaluate() функцию:
 const node2 = math.parse('x^a')
const code2 = node2.compile()
let scope = {
    x: 3,
    a: 2
}
code2.evaluate(scope) // 9
 
  1. Используйте генератор синтаксического анализа, например http://zaa.ch/jison /, но это кажется излишним для того, что я хочу сделать..

Честно говоря, я считаю, что использование eval() в моем конкретном случае оправдано: динамические формулы с динамическими значениями. Я мог бы использовать внешнюю библиотеку, например mathjs , но я чувствую, что она мне не нужна для таких простых операций.

Я бы очень хотел получить вашу идею по этому вопросу и услышать ваше предложение, если таковое имеется!

PS: Да, вопрос уже был задан. Хотя самые похожие вопросы, которые я нашел, были заданы (и на которые были даны ответы) несколько лет назад. Я хотел бы получить свежий вклад в этот вопрос.


Основываясь на ответе @Sleavely и дальнейшем чтении по теме, mathjs представляется наиболее разумным решением для принятия 😉

Комментарии:

1. как бы вы помешали пользователям вводить проблемный ввод? xkcd.com/327 вы будете проводить дезинфекцию?

2. Сторонние администраторы могут быть единственными, кто может редактировать код, но где он будет выполняться на самом деле? В вашем бэкэнде? На компьютерах посетителей? Я настоятельно рекомендую вам избегать eval(), чтобы вас не обвинили в том, что посетители биткойн-майнера в конечном итоге получают от вашего приложения. Имея это в виду, я думаю, что вы на правильном пути со своим 2-м вариантом. Оценка формулы по набору предопределенных переменных кажется мне достаточно безопасной. Похоже mathjs , активно избегает eval(): github.com/josdejong/mathjs/blob/master/docs/expressions /…

3. Могу я предложить mathjs.org/index.html вместо того, чтобы использовать ванильный eval().

4. @malarres Да, я бы, конечно, очистил ввод. Входные данные должны быть только с плавающей запятой.

5. @Sleavely Спасибо за ссылки. Тбх, я думал, что mathjs работает eval() за сценой. Вот почему мне было интересно, зачем добавлять библиотеку, если я могу сделать «то же самое». У меня такое чувство mathjs , что это среднее решение между плохим eval() и сложным (?) jison решением

Ответ №1:

На основе ответа @Sleavely (см. Комментарий, приведенный Ниже) и дальнейшего чтения по теме, mathjs представляется наиболее разумным решением:

Сторонние администраторы могут быть единственными, кто может редактировать код, но где он будет выполняться на самом деле? В вашем бэкэнде? На компьютерах посетителей? Я настоятельно рекомендую вам избегать eval(), чтобы вас не обвинили в том, что посетители биткойн-майнера в конечном итоге получают от вашего приложения. Имея это в виду, я думаю, что вы на правильном пути со своим 2-м вариантом. Оценка формулы по набору предопределенных переменных кажется мне достаточно безопасной. Похоже, mathjs активно избегает eval(): https://github.com/josdejong/mathjs/blob/master/docs/expressions/security.md