В практике программирования на 1С часто возникают задачи, связанные с интерпретацией строковых выражений и их вычислением. Казалось бы, простые арифметические операции должны работать предсказуемо. Однако, если мы используем функцию Вычислить() для обработки данных, введенных пользователем или полученных из внешних систем, мы можем столкнуться с неочевидными особенностями платформы. Рассмотрим интересную задачу с форума, которая демонстрирует разницу между преобразованием типов и компиляцией выражений.
Представим ситуацию: у нас есть функция, которая проверяет корректность работы вычислительного модуля. На вход подаются две строки, содержащие числа. Логика проверки кажется надежной:
Число()) совпадают с результатом Вычислить().А + "+" + Б и Б + "+" + А.Код функции выглядит следующим образом:
Функция ВычислительРаботаетКорректно()
А = "1.0";
Б = "0.1";
// Предварительная проверка
Если СтрДлина(А) <> СтрДлина(Б)
ИЛИ Число(А) <> Вычислить(А)
ИЛИ Число(Б) <> Вычислить(Б) Тогда
Сообщить("Обнаружено некорректное вмешательство!");
Возврат Ложь;
КонецЕсли;
КонтрольноеВыражение1 = А + "+" + Б; // А+Б
КонтрольноеВыражение2 = Б + "+" + А; // Б+А
// Проверка на коммутативность сложения
Возврат Вычислить(КонтрольноеВыражение1) = Вычислить(КонтрольноеВыражение2);
КонецФункции
Однако выяснилось, что если "агенты" (или просто некорректный ввод данных) изменят строки определенным образом, функция Вычислить() может выдать ошибку, и приложение упадет. Давайте разберемся, почему это происходит и в чем заключается уязвимость.
Главная проблема кроется в том, как платформа 1С воспринимает числа с плавающей точкой, у которых опущена целая часть (например, .1 вместо 0.1).
Рассмотрим, как работают разные механизмы платформы с такой строкой:
Число(".1"): Это механизм приведения типов (soft casting). Платформа понимает, что пользователь имел в виду число 0.1, и успешно преобразует строку в число.Вычислить(".1"): Эта функция запускает полноценный синтаксический анализатор встроенного языка. В грамматике языка 1С символ "точка" является оператором обращения к свойствам или методам. Когда анализатор видит строку, начинающуюся с точки, он ожидает объект перед ней. Числовой литерал в коде 1С обязан иметь цифру перед точкой (например, 0.1). Запись .1 является синтаксически некорректной для компилятора.Именно поэтому "вмешательство", при котором строки превращаются в А = "1." и Б = ".1", ломает логику.
Если мы составим выражение КонтрольноеВыражение2 = Б + "+" + А, то получим строку ".1+1.". При попытке выполнить Вычислить(".1+1.") платформа выдаст синтаксическую ошибку, так как выражение начинается с точки, которая не воспринимается как часть числа.
Разберем несколько способов, как защитить код от подобных ситуаций и гарантировать корректность вычислений.
Самый простой способ избежать падения приложения (краша) — это обернуть опасный вызов в блок обработки исключений. Это не объясняет причину ошибки, но позволяет программе продолжить работу.
КонтрольноеВыражение1 = А + "+" + Б;
КонтрольноеВыражение2 = Б + "+" + А;
Попытка
// Если одно из выражений некорректно, мы попадем в Исключение
Возврат Вычислить(КонтрольноеВыражение1) = Вычислить(КонтрольноеВыражение2);
Исключение
// Значит, строки содержат недопустимые для парсера символы
Возврат Ложь;
КонецПопытки;
Поскольку мы выяснили, что корень проблемы — в отсутствии ведущего нуля перед точкой, мы можем добавить явную проверку этого условия. Это решение "в лоб", но оно эффективно закрывает конкретную уязвимость.
// Проверка, не начинается ли число с точки
Если Лев(А, 1) = "." ИЛИ Лев(Б, 1) = "." Тогда
Сообщить("Некорректный формат числа: отсутствует ведущий ноль!");
Возврат Ложь;
КонецЕсли;
Наиболее надежный способ проверки того, что строка является корректным числом с точки зрения платформы и стандартов обмена данными — использование механизмов XML-сериализации. Метод XMLЗначение работает строже, чем Число, но безопаснее, чем Вычислить.
Алгоритм следующий: мы пытаемся преобразовать нашу строку в число, а затем обратно в каноническую строку XML. Если результат совпадает с эталонным преобразованием, значит, формат корректен.
// Функция для строгой проверки
Попытка
// Пробуем получить значение типа Число из строки А
ЗначениеА = XMLЗначение(Тип("Число"), А);
// Проверяем обратную совместимость или формат
// В данном контексте достаточно убедиться, что XMLЗначение не упало
// и результат адекватен.
// Более простая проверка на соответствие формату XML:
Если НЕ (XMLСтрока(Число(А)) = XMLЗначение(Тип("Строка"), А)) Тогда
// Логика проверки может варьироваться, но суть в использовании XML-методов
КонецЕсли;
Исключение
Возврат Ложь;
КонецПопытки;
Однако наиболее элегантное решение, предложенное в обсуждении, основано на сравнении строкового представления.
Самое правильное архитектурное решение — избегать использования Вычислить() для простых арифметических операций над строками. Вместо этого следует явно преобразовывать строки в числа и работать с ними. Это исключает этап синтаксического разбора выражения и связанные с ним ошибки парсера.
Функция ВычислительРаботаетКорректноБезопасно()
А = "1.";
Б = ".1"; // Даже такой ввод будет обработан корректно через Число()
// Используем Число() для безопасного преобразования
// Число(".1") вернет 0.1, ошибки не будет
ЧислоА = Число(А);
ЧислоБ = Число(Б);
КонтрольноеВыражение1 = ЧислоА + ЧислоБ;
КонтрольноеВыражение2 = ЧислоБ + ЧислоА;
// Сравниваем результаты
Возврат КонтрольноеВыражение1 = КонтрольноеВыражение2;
КонецФункции
Число() и функция Вычислить() используют разные алгоритмы разбора строки. Число() более "всеядна" и допускает форматы вроде .1, тогда как Вычислить() требует строгого соответствия синтаксису языка 1С (наличие ведущего нуля: 0.1).Вычислить() как синтаксическая ошибка или некорректное обращение к контексту, что приводит к аварийному завершению кода, если не использовать Попытка...Исключение.Вычислить() для простых математических операций. Используйте явное приведение типов через Число().Применяя эти знания, вы сможете создавать более надежные алгоритмы проверки и обработки пользовательских данных, устойчивые к нестандартному форматированию.