flang компилятор доказывает, что программа не зациклится 0.6.2 GitHub

Два исполнителя портили двоичный поток по-разному, и расхождение было не в кодировке, а в том, где каждый терял

Хозяин на Node и хозяин, напечатанный в C, читали и писали одно и то же поручение и теряли байты в разных местах. Это важнее самой порчи: программа, проверенная под одним хозяином, под другим ехала не так, и увидеть это можно было только на двоичных данных, которых в корпусе не было.

Node терял на чтении. сокет.setEncoding("utf8") (flang/src/host/node.mjs) заменял всякий не-UTF-8 октет знаком U+FFFD. Замена необратима. На 256 октетах подряд теряется ровно 128 — всё, что старше 0x7F.

C терял на записи. write(fd, text, strlen(text)) (flang/src/emit/c/flang_repl.c) обрывал содержимое на первом нулевом октете, а на чтении отдавал байты сырыми. То есть двоичный протокол не работал ни под одним из двух, и не работал по-разному: один портил вход, другой выход.

Вывод, который стоит держать при всякой правке этого слоя: «хозяева ведут себя одинаково» — это утверждение, а не умолчание, и проверяется оно только на входах, которых текстовый корпус не содержит. Пара октетных поручений лечит обоих разом именно потому, что переводить в ней нечего: список чисел уезжает в write и приезжает из read без единого решения хозяина.

Чем подтверждено. Ветка vypusk/hozyain. Замер порчи Node — flang/test/oktety.test.mjs, проверка «УЛИКА: на всех 256 октетах текстовая пара теряет»: 128 знаков замены, обратный перевод даёт 512 октетов вместо 256. Место порчи в C — чтение кода, strlen на io_order_text; отдельного прогона до правки не делалось.

Чем ограничено. Расхождение на ТЕКСТОВОЙ паре осталось: C по-прежнему отдаёт сырые байты там, где Node отдаёт раскодированный UTF-8 со знаками замены. На законном UTF-8 они совпадают, на испорченном — нет. Закрывать это надо раскодировщиком в C, и это отдельная работа.

Связано: octets-are-expressible-as-a-list-of-numbers-not-as-a-string