Самомодифицирующийся код — программный приём, при котором приложение создаёт или изменяет часть своего программного
кода во время выполнения.
Большинство современных процессоров имеет раздельные области кэша для кода и для данных, поэтому
изменение кода "на лету" невозможно (но ранее применялось в полиморфных вирусах).
Может применяться к модулям программы до их запуска с целью отключения неиспользуемых функций,
например, журналирования.
В JIT по результатам анализа создается оптимизированная версия кода и происходит
переключение выполнения на новый код.
Возможность самомодификации в интерпретируемых языках может использовать следующие технологии:
* Гомоиконичность (homoiconicity) - текст программы имеет такую же структуру, как её абстрактное синтаксическое дерево (AST).
Это позволяет всему коду на языке быть доступным и обработанным в качестве данных,
используя одно и то же представление. Этим свойством обладают языки LISP, Scheme, Prolog, Tcl.
* Рефлексия (интроспекция) - представление внутренних структур языка в виде переменных встроенных типов с возможностью
доступа к ним из программы. Позволяет во время выполнения просматривать, создавать и изменять определения типов.
Этим свойством обладают языки Java, C#, Forth, Python, PowerShell, Lua.
* Интерпретация произвольного кода, представленного в виде строки (eval).
Практически все интерпретаторы имеют такую возможность.
Обычно процесс начальной трансляции отделен от выполнения кода, но
доступность интерпретатора в момент трансляции может существенно
расширить возможности языка с помощью самомодификации.
Уже в LISP были реализованы макрокоманды, которые выполнялись
на этапе загрузки программы и заменяли свой вызов на новый список, используя список аргументов.
Таким образом в LISP можно определять новые синтаксические конструкции.
В компилируемых языках подобная возможность есть в языке Nim,
в котором AST является частью языка и есть поддержка макросов, выполняемых
после синтаксического разбора перед генерацией кода. Это позволяет определять
предметно-ориентированные языки (DSL) в рамках Nim.
Поддержка макросов есть также в Nemerle.
В C++ также есть частичная поддержка выполнения кода на этапе компиляции, используемая для оптимизации
(constexpr/consteval функции). Также есть предложения по полноценной рефлексии
во время компиляции для упрощения определения шаблонов.
```c++
#include <meta>
template<HashAlgorithm H, StandardLayoutType T>
bool hash_append(H& algo, const T& t) {
constexpr auto data_members =
std::meta::members_of(reflexpr(T),
std::meta::is_nonstatic_data_member);
for constexpr (std::meta::info member : data_members)
hash_append(algo, t.idexpr(member));
}
```
Язык Forth обычно используется для встраиваемых систем и моделирует стековый компьютер.
Особенностью языка является доступ к генерируемому коду, вызываемые функции делятся на
* выполняемые всегда;
* если интерпретатор находится в режиме компиляции определения новой функции, то вызов добавляется в генерируемый код, иначе выполняется.
Можно переключаться между режимами компиляции и интерпретации, изменять генерируемый код произвольным образом.