273 lines
39 KiB
JavaScript
273 lines
39 KiB
JavaScript
function escapeHtml(value) {
|
||
return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
||
}
|
||
|
||
const p = (text) => '<p>' + text + '</p>';
|
||
const h2 = (text) => '<h2>' + text + '</h2>';
|
||
const code = (text) => '<pre><code>' + escapeHtml(text) + '</code></pre>';
|
||
const ol = (items) => '<ol>' + items.map((item) => '<li>' + item + '</li>').join('') + '</ol>';
|
||
const figure = (src, alt, caption) => '<figure><img src="' + src + '" alt="' + escapeHtml(alt) + '" loading="lazy" /><figcaption>' + caption + '</figcaption></figure>';
|
||
const table = (caption, headers, rows) => '<div class="table-scroll"><table><caption>' + caption + '</caption><thead><tr>' + headers.map((cell) => '<th scope="col">' + cell + '</th>').join('') + '</tr></thead><tbody>' + rows.map((row) => '<tr>' + row.map((cell) => '<td>' + cell + '</td>').join('') + '</tr>').join('') + '</tbody></table></div>';
|
||
|
||
function plainText(html) {
|
||
return html.replace(/<[^>]+>/g, ' ').replace(/&(?:quot|amp|lt|gt|#039);/g, ' ').replace(/\s+/g, ' ').trim();
|
||
}
|
||
|
||
function bodyText(html) {
|
||
return plainText(html.replace(/<h2>Проверяемые источники<\/h2>[\s\S]*?(?=<h2>|$)/, ''));
|
||
}
|
||
|
||
const REFERENCES = Object.freeze({
|
||
functions: {
|
||
title: 'D Language Specification: Functions and Function Safety',
|
||
url: 'https://dlang.org/spec/function.html',
|
||
version: 'D language specification, page generated 25 July 2026',
|
||
},
|
||
memory: {
|
||
title: 'D Language Specification: Memory Safety',
|
||
url: 'https://dlang.org/spec/memory-safe-d.html',
|
||
version: 'D language specification, page generated 23 July 2026',
|
||
},
|
||
abi: {
|
||
title: 'D Language Specification: Application Binary Interface',
|
||
url: 'https://dlang.org/spec/abi.html',
|
||
version: 'D language specification, page checked 31 July 2026',
|
||
},
|
||
cInterface: {
|
||
title: 'D Language Specification: Interfacing to C',
|
||
url: 'https://dlang.org/spec/interfaceToC.html',
|
||
version: 'D language specification, page checked 31 July 2026',
|
||
},
|
||
});
|
||
|
||
function sources(entries) {
|
||
return '<ul>' + entries.map(({ key, use, boundary }) => {
|
||
const reference = REFERENCES[key];
|
||
return '<li><a href="' + reference.url + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(reference.title) + '</a> — версия и дата: ' + escapeHtml(reference.version) + '. Применение: ' + escapeHtml(use) + ' Граница: ' + escapeHtml(boundary) + '</li>';
|
||
}).join('') + '</ul>';
|
||
}
|
||
|
||
export function validateDWorkload(input) {
|
||
const throughput = Number(input?.throughput ?? 0);
|
||
const latencyBudgetMs = Number(input?.latencyBudgetMs ?? 0);
|
||
const nativeBoundary = Boolean(input?.nativeBoundary);
|
||
const deploymentTargets = Number(input?.deploymentTargets ?? 0);
|
||
if (!Number.isFinite(throughput) || throughput <= 0) return { decision: 'reject', reason: 'нужна измеримая нагрузка' };
|
||
if (latencyBudgetMs <= 0) return { decision: 'reject', reason: 'нужен бюджет задержки' };
|
||
if (deploymentTargets > 2 && !nativeBoundary) return { decision: 'compare', reason: 'широкий target matrix требует сравнить toolchain' };
|
||
if (nativeBoundary && throughput > 10000) return { decision: 'consider-d', reason: 'нагрузка и нативная граница дают основание проверить D' };
|
||
return { decision: 'keep-current-tool', reason: 'ограничения не требуют смены языка' };
|
||
}
|
||
|
||
export function checkSafeBoundary(input) {
|
||
const capacity = Number(input?.capacity ?? 0);
|
||
const declaredLength = Number(input?.declaredLength ?? 0);
|
||
const pointerChecked = Boolean(input?.pointerChecked);
|
||
if (!pointerChecked) return { class: 'system', action: 'проверить указатель и владельца буфера вручную' };
|
||
if (!Number.isInteger(declaredLength) || declaredLength < 0 || declaredLength > capacity) return { class: 'reject', action: 'остановить вызов: длина выходит за буфер' };
|
||
return { class: 'safe-interface', action: 'передать slice с проверенной длиной' };
|
||
}
|
||
|
||
export function validateCAbiPacket(packet, contract) {
|
||
const errors = [];
|
||
if (packet?.byteLength !== contract?.byteLength) errors.push('размер структуры');
|
||
if (packet?.endianness !== contract?.endianness) errors.push('порядок байтов');
|
||
for (const field of contract?.fields ?? []) {
|
||
if (!Object.hasOwn(packet ?? {}, field.name)) errors.push('поле ' + field.name);
|
||
}
|
||
return { accepted: errors.length === 0, errors };
|
||
}
|
||
|
||
function revision(meta, parts, referenceEntries) {
|
||
const contentHtml = parts.join('') + h2('Проверяемые источники') + sources(referenceEntries);
|
||
const proseLength = bodyText(contentHtml).length;
|
||
if (proseLength < 5000 || proseLength > 15000) throw new Error(meta.slug + ': body length ' + proseLength);
|
||
return Object.freeze({ ...meta, contentHtml, proseLength });
|
||
}
|
||
|
||
const practice = revision({
|
||
slug: 'editorial-2027-03-practice-d-lessons',
|
||
title: 'D для прикладной утилиты: сначала контракт входа, потом язык',
|
||
categories: ['D', 'Инженерные практики'],
|
||
cover: '/assets/editorial/2027/d-lessons-2027-runtime-tradeoff-map.svg',
|
||
excerpt: 'Как понять, есть ли у задачи основание для D: нагрузка, native boundary, target matrix и стоимость toolchain.',
|
||
readingMinutes: 15,
|
||
}, [
|
||
p('Проблема выбора языка для утилиты начинается с красивого свойства: D компилируется в native binary, поддерживает контрактные проверки и даёт доступ к C ABI. Если принять это свойство за готовое решение, небольшая команда получает новый компилятор, пакетный менеджер и набор правил сборки, хотя исходная задержка могла возникать в запросе, формате файла или неверном контракте входа. Цена — месяцы сопровождения ради проблемы, которую язык не решает.'),
|
||
p('Чтобы решение было инженерным, нужно сначала описать workload: сколько элементов проходит через программу, какой бюджет задержки, какие target-платформы, есть ли C-библиотека и как будет собираться бинарник. D появляется в таблице как один вариант рядом с текущим языком. Этот порядок не принижает язык. Он защищает проект от решения по вкусу и оставляет проверяемый критерий, когда выбор оправдан.'),
|
||
h2('Что именно хотим улучшить'),
|
||
p('«Нужна производительность» — слишком широкая формулировка. Для CLI важны время запуска, скорость обработки, память и размер артефакта. Для сервиса добавляется модель конкуренции, timeout и наблюдаемость. Для инструмента около C-библиотеки важны соглашение вызова, layout структуры и способ владения памятью. Один и тот же язык может быть уместен для одного пункта и лишним для другого.'),
|
||
p('Опишите минимум два кандидата. Текущий инструмент часто выигрывает скоростью разработки и готовыми библиотеками; D может выиграть у места, где важны native deployment, явная работа с памятью или C ABI. Но каждое преимущество имеет стоимость: новый toolchain, обучение, platform packages, время сборки и диагностика production binary. Сравнение должно показывать эту цену рядом с эффектом.'),
|
||
figure('/assets/editorial/2027/d-lessons-2027-runtime-tradeoff-map.svg', 'Карта выбора языка для прикладной утилиты: workload и ограничения ведут к сравнению D с текущим инструментом и проверяемому решению.', 'Схема начинает решение с нагрузки и ограничений. Ветка D появляется только после определения границы задачи, а не из-за отдельного свойства языка.'),
|
||
table('Матрица выбора D для утилиты', ['Ограничение', 'Вопрос', 'Сигнал в пользу D', 'Цена решения'], [
|
||
['Нагрузка', 'где расходуется CPU и память?', 'узкое место внутри вычисления', 'нужен профиль, а не предположение'],
|
||
['Native boundary', 'есть ли C ABI или системный вызов?', 'контракт можно проверить на границе', 'ручная проверка unsafe-участка'],
|
||
['Targets', 'сколько платформ и архитектур?', 'матрица поддерживается toolchain', 'сборки и бинарные артефакты'],
|
||
['Команда', 'кто будет читать и менять код?', 'есть owner и code review', 'обучение и время поддержки'],
|
||
['Доставка', 'как версионируется бинарник?', 'простая доставка без runtime', 'размер, лицензии, упаковка'],
|
||
]),
|
||
h2('Учебный локальный фильтр требований'),
|
||
p('Функция ниже делает не рекламный вывод, а фиксирует форму входа. В ней есть положительная ветка только тогда, когда одновременно названы нагрузка, latency budget и native boundary. Если target matrix шире двух платформ, результат предлагает сравнение, а не автоматический переход. Все числа учебные: перед решением их заменяют измерениями конкретной команды.'),
|
||
code(`import { validateDWorkload } from './upgrade-2027-03.mjs';
|
||
|
||
const inputs = [
|
||
{ throughput: 12000, latencyBudgetMs: 20, nativeBoundary: true, deploymentTargets: 1 },
|
||
{ throughput: 300, latencyBudgetMs: 500, nativeBoundary: false, deploymentTargets: 1 },
|
||
{ throughput: 12000, latencyBudgetMs: 20, nativeBoundary: false, deploymentTargets: 4 },
|
||
];
|
||
|
||
for (const input of inputs) console.log(validateDWorkload(input));
|
||
// consider-d; keep-current-tool; compare`),
|
||
p('Первый вход имеет узкую вычислительную задачу и нативную границу — D стоит проверить измерением. Второй не требует смены toolchain по заданным ограничениям. Третий слишком широк для одного выбора: сначала нужно сравнить способы сборки и доставки на всех targets. Функция полезна как шаблон карточки требований, а не как замена профилированию.'),
|
||
h2('Контрактная проверка в D'),
|
||
p('У D есть function contracts: precondition через <code>in</code> и postcondition через <code>out</code>. Это не универсальная валидация входа и не замена тестам. Контракт полезен, когда условие принадлежит самой функции: размер диапазона, допустимый индекс, инвариант результата. Для пользовательского файла всё равно нужна отдельная ошибка с безопасным сообщением и понятным форматом.'),
|
||
p('При выборе языка не обещайте, что contract автоматически ускорит программу или найдёт бизнес-ошибку. Он проверяет условие в точке выполнения и зависит от режима сборки. Важнее сначала назвать, кто владеет условием: parser, domain service или boundary с C. Тогда один и тот же контракт можно повторить в тесте и в обработчике ошибки, не пряча смысл в assertion.'),
|
||
h2('Действия по порядку'),
|
||
ol([
|
||
'Записать единицу нагрузки, бюджет задержки, размер данных, target-платформы и входные ограничения.',
|
||
'Найти измеряемый bottleneck и проверить, находится ли он внутри кода, который язык действительно изменит.',
|
||
'Сравнить D и текущий вариант по сборке, библиотекам, отладке, размеру бинарника и навыкам поддержки.',
|
||
'Для нативной границы описать C ABI, владение памятью и ошибку; unsafe-участок выделить отдельно.',
|
||
'Собрать минимальный прототип с одним workload и одинаковой методикой замера, затем зафиксировать результат и цену поддержки.',
|
||
]),
|
||
h2('Ограничения и следующий шаг'),
|
||
p('Фильтр требований не измеряет скорость и не говорит, что D лучше. Порог throughput в примере вымышленный и не переносится на другое железо. Контракты не устраняют логические ошибки, зависимость от внешней библиотеки или стоимость сборки. D также не делает код переносимым автоматически: platform ABI, linker и runtime остаются частью решения.'),
|
||
p('Следующий шаг — взять одну горячую функцию и сделать парный прототип на текущем языке и D с одинаковым входом и выходом. Замерьте cold start, steady-state, память и время разработчика на исправление намеренной ошибки. Решение «остаться» будет таким же полезным результатом, как переход, если оно опирается на эти поля.'),
|
||
], [
|
||
{ key: 'functions', use: 'Function contracts и атрибуты D используются для объяснения pre/post conditions и границы функции.', boundary: 'Спецификация не выбирает язык для продукта и не даёт benchmark конкретного workload.' },
|
||
{ key: 'memory', use: 'Категории @safe, @trusted и @system используются при оценке нативной границы.', boundary: 'Memory safety не гарантирует отсутствие логических, portability и performance ошибок.' },
|
||
{ key: 'abi', use: 'ABI-граница включена в матрицу выбора как часть доставки бинарника.', boundary: 'Спецификация ABI не описывает настройки конкретного компилятора и платформы.' },
|
||
]);
|
||
|
||
const mechanism = revision({
|
||
slug: 'editorial-2027-03-mechanism-d-lessons',
|
||
title: 'D: @safe и @trusted на границе C API',
|
||
categories: ['D', 'Безопасность памяти'],
|
||
cover: '/assets/editorial/2027/d-lessons-2027-constraint-matrix.svg',
|
||
excerpt: 'Как провести маленький unsafe-участок через проверенный интерфейс и не считать атрибут @safe доказательством всей системы.',
|
||
readingMinutes: 16,
|
||
}, [
|
||
p('Проблема FFI-кода появляется там, где D вызывает C-функцию с указателем и отдельной длиной буфера. Если длина пришла из другого источника, вызов может прочитать за пределами памяти, даже когда внешний метод выглядит коротким. Цена ошибки — повреждение памяти, падение процесса или уязвимость, которую трудно воспроизвести по обычному input. Один атрибут на публичной функции не исправляет неверное условие границы.'),
|
||
p('В D для этой границы различаются @safe, @trusted и @system. @safe-код ограничивает операции, которые могут привести к memory corruption. @trusted разрешает узкий участок, но ответственность за его интерфейс остаётся у автора. @system не даёт компилятору такого обещания. Механизм работает, если unsafe-код короткий, его входы проверены, а наружу выходит безопасное представление данных.'),
|
||
h2('Сначала проверяем размер, потом вызываем C'),
|
||
p('C-функция часто получает <code>pointer + length</code>. Сам указатель не содержит длину, поэтому компилятор не может вывести, что заявленный диапазон действителен. В D безопасный wrapper должен принять массив или slice, проверить нужное условие и передать только диапазон, размер которого известен. Если C API требует null-terminated string, одного массива байт тоже недостаточно: нужна отдельная проверка завершающего байта.'),
|
||
p('Изолируйте правила владельца. Если C-функция сохраняет указатель после возврата, wrapper должен либо запретить такой вызов, либо передать копию с понятным временем жизни. Атрибут <code>scope</code> помогает выражать ограничения escape там, где включена соответствующая проверка, но он не заменяет договорённость с внешней библиотекой. Любая функция, которая сохраняет адрес, требует отдельного чтения API и теста.'),
|
||
figure('/assets/editorial/2027/d-lessons-2027-constraint-matrix.svg', 'Матрица границы D и C API: размер буфера, владелец, атрибут безопасности и допустимый результат проверки.', 'Схема связывает техническое ограничение с проверкой и стоп-условием. Зелёный путь начинается только после проверки длины и времени жизни.'),
|
||
table('Роли атрибутов на FFI-границе', ['Уровень', 'Что разрешает', 'Что обязан проверить инженер', 'Типичная ошибка'], [
|
||
['@safe', 'ограниченный набор операций', 'что вызовы и значения остаются безопасными', 'считать весь вызванный C безопасным'],
|
||
['@trusted', 'узкая ручная обёртка', 'инвариант указателя, длины и lifetime', 'поместить большой модуль в trusted'],
|
||
['@system', 'низкоуровневые операции', 'каждый callsite и контракт ABI', 'передать raw pointer без проверки'],
|
||
['slice', 'указатель и длина вместе', 'что slice не выходит за объект', 'довериться внешней length'],
|
||
]),
|
||
h2('Локальная проверка буфера'),
|
||
p('Вместо вызова реальной C-библиотеки сначала можно прогнать boundary checker на данных теста. Он принимает capacity, заявленную длину и признак проверки указателя. Результат разделяет отсутствие проверки, неверный диапазон и безопасный интерфейс. Это предметный пример входа в FFI: он проверяет именно опасную пару pointer/length, а не абстрактный статус карточки.'),
|
||
code(`import { checkSafeBoundary } from './upgrade-2027-03.mjs';
|
||
|
||
const calls = [
|
||
{ capacity: 16, declaredLength: 8, pointerChecked: true },
|
||
{ capacity: 16, declaredLength: 24, pointerChecked: true },
|
||
{ capacity: 16, declaredLength: 8, pointerChecked: false },
|
||
];
|
||
|
||
for (const call of calls) console.log(checkSafeBoundary(call));
|
||
// safe-interface; reject; system`),
|
||
p('Первый вход даёт диапазон внутри буфера. Второй останавливается до вызова: внешний контракт обещает 24 байта, а доступно 16. Третий не принимает решение за инженера, потому что адрес не прошёл проверку владельца. В D такой проверкой должен владеть маленький wrapper, а в тесте нужны граничные значения 0, capacity и capacity+1.'),
|
||
h2('Что означает @trusted'),
|
||
p('@trusted — не «проверено компилятором». Это обещание, что внешняя форма функции безопасна, хотя тело содержит операции, которые компилятор не может проверить. Поэтому у trusted-функции должны быть короткий исходник, явные preconditions и тесты на invalid length, null, пустой slice и повторный вызов. Не прячьте в ней преобразование формата, ownership и обработку ошибок одновременно.'),
|
||
p('Если внешняя C-функция возвращает указатель, проверка должна ответить на два вопроса: объект жив и его размер известен? При ответе «нет» безопасный интерфейс невозможен без копирования или дополнительного контракта. После вызова нельзя использовать старый slice, если C-функция освобождает память. Ошибка lifetime часто переживает тесты на успешном input, поэтому негативная матрица обязательна.'),
|
||
h2('Действия по порядку'),
|
||
ol([
|
||
'Прочитать C-прототип и зафиксировать смысл каждого указателя, длины, возвращаемого адреса и кода ошибки.',
|
||
'Выделить минимальный wrapper; не переносить внутрь @trusted парсинг, бизнес-правила и сетевой код.',
|
||
'Проверять указатель, диапазон, нулевую длину, overflow и время жизни до перехода в C.',
|
||
'Поставить unit tests на валидные и граничные значения, затем прогнать sanitizers или инструменты платформы.',
|
||
'Оставить публичную функцию @safe только при доказанном безопасном интерфейсе; остальную зону явно маркировать @system.',
|
||
]),
|
||
h2('Ограничения и следующий шаг'),
|
||
p('Проверка capacity в JavaScript — учебная модель числовой границы, а не анализ D-памяти. Она не видит aliasing, alignment, calling convention, null termination или освобождение в C. Даже корректный @safe wrapper может передать семантически неверный enum или структуру. Нужен compile-time и runtime тест именно тем компилятором и ABI, с которыми собирается продукт.'),
|
||
p('Следующий шаг — выбрать один extern(C) вызов и оформить для него таблицу: pointer, length, ownership, error, thread-safety. Напишите маленький wrapper, который принимает D slice, и отдельно проведите review trusted-тела. Если один из пунктов не имеет ответа, вызов нельзя считать готовым к безопасной границе.'),
|
||
], [
|
||
{ key: 'memory', use: 'Определения @safe, @trusted, @system, scope и границы memory safety.', boundary: 'Спецификация не проверяет контракт внешней C-библиотеки и не гарантирует portability или отсутствие логических ошибок.' },
|
||
{ key: 'functions', use: 'Правила function attributes и contract expressions используются для precondition и postcondition.', boundary: 'Документация не создаёт unit tests и не решает lifetime конкретного объекта.' },
|
||
{ key: 'cInterface', use: 'Интерфейс D/C и отдельные соглашения вызова используются для описания wrapper boundary.', boundary: 'Страница не подтверждает прототип, ABI и ownership неизвестной библиотеки.' },
|
||
]);
|
||
|
||
const field = revision({
|
||
slug: 'editorial-2027-03-field-d-lessons',
|
||
title: 'D и C ABI: разобрать пакет до вызова',
|
||
categories: ['D', 'Интеграции'],
|
||
cover: '/assets/editorial/2027/d-lessons-2027-evidence-handoff-loop.svg',
|
||
excerpt: 'Полевой разбор FFI-ошибки: размер структуры, порядок байтов и обязательные поля проверяются до вызова C.',
|
||
readingMinutes: 15,
|
||
}, [
|
||
p('Проблема C ABI редко выглядит как ошибка на строке вызова. D-программа передаёт структуру, C читает её и возвращает код, но значение поля оказывается неверным или процесс падает только на одной архитектуре. Цена — непредсказуемый сбой на границе, где обычный unit test видит только один компилятор и одну раскладку памяти. Чем дольше ошибка живёт, тем труднее отличить формат данных от ошибки бизнес-логики.'),
|
||
p('Полевой разбор начинается с пакета, который реально пересекает ABI: byte length, endianness, поля, alignment и calling convention. Дальше нужно сопоставить его с C header и настройками компилятора. Нельзя проверять только имя struct. Два типа с одинаковыми полями могут иметь разный padding, порядок байтов или размер указателя. Пакет — это физический контракт, а не только исходный текст.'),
|
||
h2('Размер структуры — первый стоп'),
|
||
p('ABI определяет, как типы и функции представлены для взаимодействия с машинным кодом. Для структуры важны не только поля, но и выравнивание. Добавленный <code>int</code> может изменить offsets следующего поля; на 32- и 64-битной платформе размер указателя различается. Если D и C собраны с разными ожиданиями, чтение смещается, а ошибка проявится как «неверное значение» далеко от причины.'),
|
||
p('Порядок байтов — отдельная ось. Файл может быть little-endian, а внешний протокол — big-endian; автоматическое копирование структуры не является преобразованием формата. Для числового поля запишите wire representation и проверяйте её на известном значении вроде <code>0x01020304</code>. Так видно, поменялись байты или перепутана длина пакета.'),
|
||
figure('/assets/editorial/2027/d-lessons-2027-evidence-handoff-loop.svg', 'Цикл проверки D и C ABI: пакет, размер, поля и порядок байтов проходят сопоставление с контрактом до вызова функции.', 'Схема отделяет физическую проверку пакета от самого вызова. Красная ветка возвращает данные на границу, если размер или поле не совпали.'),
|
||
table('Проверка ABI-пакета до FFI-вызова', ['Проверка', 'Пример входа', 'Ожидаемое действие', 'Если пропустить'], [
|
||
['byte length', '24 байта', 'сверить sizeof на обеих сторонах', 'смещение полей'],
|
||
['endianness', 'little-endian', 'декодировать число явно', 'неверный id или размер'],
|
||
['field set', 'version, flags, payload', 'проверить обязательные поля', 'чтение мусора'],
|
||
['alignment', 'offset 8 вместо 4', 'сверить compiler layout', 'сбой на другой архитектуре'],
|
||
['error code', '0 или отрицательное значение', 'перевести в D-ошибку', 'успех при частичном чтении'],
|
||
]),
|
||
h2('Учебный валидатор пакета'),
|
||
p('Локальная функция ниже принимает пакет и описание ожидаемой структуры. Она проверяет три вещи, которые можно увидеть ещё до вызова: размер, порядок байтов и набор полей. Числа и названия в примере учебные, но сам порядок повторяет рабочую проверку. В реальном проекте contract строится из header и результатов компилятора, а не из догадки автора wrapper.'),
|
||
code(`import { validateCAbiPacket } from './upgrade-2027-03.mjs';
|
||
|
||
const contract = {
|
||
byteLength: 12,
|
||
endianness: 'little',
|
||
fields: [{ name: 'version' }, { name: 'flags' }, { name: 'payload' }],
|
||
};
|
||
const packet = { byteLength: 12, endianness: 'little', version: 2, flags: 1, payload: 4096 };
|
||
const broken = { byteLength: 16, endianness: 'big', version: 2, flags: 1 };
|
||
|
||
console.log(validateCAbiPacket(packet, contract));
|
||
console.log(validateCAbiPacket(broken, contract));
|
||
// { accepted: true, errors: [] }
|
||
// { accepted: false, errors: ['размер структуры', 'порядок байтов', 'поле payload'] }`),
|
||
p('Валидатор не вызывает C и поэтому не доказывает, что ABI корректен. Он делает видимыми три несовпадения до опасной операции. Для полноценного теста добавьте golden bytes, сборку маленького C helper и проверку результата на каждой целевой архитектуре. Смысл локального примера в том, что ошибка в contract table должна быть заметна раньше падения процесса.'),
|
||
h2('Где заканчивается автоматическая проверка'),
|
||
p('Размер и поля можно сравнить автоматически, но ownership и смысл flags требуют чтения C API. Поле <code>payload</code> может быть указателем, длиной или offset внутри того же пакета. Значение <code>0</code> может означать пусто, success или null. Не называйте результат «валидным пакетом», пока не проверены эти семантические значения и код ошибки.'),
|
||
p('Особенно опасен частичный успех. C-функция могла записать структуру, но вернуть ошибку; D-код видит заполненное поле и продолжает обработку. Обёртка должна сначала проверить код возврата, затем интерпретировать output по версии и только потом отдавать его доменному коду. Версия пакета должна быть частью ключа выбора декодера, а не обычным полем, которое можно проигнорировать.'),
|
||
h2('Действия по порядку'),
|
||
ol([
|
||
'Сохранить точный C header, compiler flags, target architecture и calling convention рядом с исходником wrapper.',
|
||
'Составить layout table с размером, offset, alignment, типом и смыслом каждого поля.',
|
||
'Проверить golden bytes для little/big-endian и граничных значений длины до вызова внешней функции.',
|
||
'Разделить код возврата, output и ошибку; не считать частично заполненную структуру успешным результатом.',
|
||
'Собрать тест на каждой поддерживаемой архитектуре и сохранить hex-пакет, версию контракта и итог проверки.',
|
||
]),
|
||
h2('Ограничения и следующий шаг'),
|
||
p('JavaScript-валидатор не моделирует padding, pointer alignment, compiler lowering и реальные байты. Он проверяет форму contract table, поэтому не заменяет C helper, D compiler и тест на целевом ABI. Документ D описывает правила языка, но конкретный vendor header может добавлять свои packing directives, версии и ownership соглашения.'),
|
||
p('Следующий шаг — выбрать один extern(C) вызов, получить маленький golden packet и сравнить layout D/C в автоматической проверке. В отчёте оставьте hex, размер, архитектуру и код возврата. Это позволит отличить изменение компилятора от изменения данных и быстро вернуть ошибку к физической границе.'),
|
||
], [
|
||
{ key: 'abi', use: 'Правила D ABI используются для объяснения layout, alignment и представления типов.', boundary: 'Спецификация не знает vendor header, compiler flags и архитектуру конкретного проекта.' },
|
||
{ key: 'cInterface', use: 'Правила extern(C) и взаимодействия с C используются для выбора contract table и calling convention.', boundary: 'Документация не гарантирует корректность неизвестного прототипа или ownership.' },
|
||
{ key: 'memory', use: 'Граница memory safety используется для отделения безопасного анализа пакета от raw pointer операций.', boundary: 'Проверка памяти не подтверждает семантику полей и код возврата внешней библиотеки.' },
|
||
]);
|
||
|
||
export const revisions = Object.freeze([practice, mechanism, field]);
|
||
|
||
export function verifyRevisionsAgainstFixture() {
|
||
const checks = revisions.map((item) => {
|
||
const body = bodyText(item.contentHtml);
|
||
return body.length >= 5000 && body.length <= 15000 && /<table>/.test(item.contentHtml) && /<figure>/.test(item.contentHtml) && /<pre><code>/.test(item.contentHtml) && /<ol>/.test(item.contentHtml) && !/(synthetic-plan-hand-off|productionEffect|future-only|plan\/scenario|source cutoff|not-collected|not-attempted|future owner|развитие автора)/i.test(body);
|
||
});
|
||
const workload = validateDWorkload({ throughput: 12000, latencyBudgetMs: 20, nativeBoundary: true, deploymentTargets: 1 });
|
||
const abi = validateCAbiPacket({ byteLength: 4, endianness: 'little', version: 1 }, { byteLength: 4, endianness: 'little', fields: [{ name: 'version' }] });
|
||
const fixtureOk = workload.decision === 'consider-d' && abi.accepted;
|
||
return Object.freeze({ passed: checks.filter(Boolean).length + (fixtureOk ? 1 : 0), total: checks.length + 1, accepted: checks.every(Boolean) && fixtureOk, characters: Object.fromEntries(revisions.map((item) => [item.slug, bodyText(item.contentHtml).length])) });
|
||
}
|
||
|
||
if (process.argv.includes('--verify-fixture')) {
|
||
const result = verifyRevisionsAgainstFixture();
|
||
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
||
if (!result.accepted) process.exitCode = 1;
|
||
}
|
||
|
||
if (process.argv.includes('--print-revisions')) process.stdout.write(JSON.stringify(revisions) + '\n');
|