Пиков / КАПО
Как читать авторский курс и нормативные материалы

Это учебные материалы, а не заключение о соответствии конкретного продукта. Организационные модели курса отделяйте от терминов стандарта: ППК в опубликованном проекте — машиночитаемый перечень, а живой реестр его версий является практикой сопровождения. Термин OSA в разных материалах имеет разный охват. Статус и обязательность нормативного документа проверяются отдельно; проект КАПО опубликован как проект первой редакции.

include не равен зависимости

Почему это важно для SCA/OSA

Соблазн собрать состав C/C++ по #include велик: заголовки лежат в исходниках, их легко грепнуть, и кажется, что #include <openssl/ssl.h> — это и есть зависимость от OpenSSL. Это ложный сигнал в обе стороны. #include означает лишь, что компилятору показали интерфейс; он не говорит ни какая версия используется, ни слинкован ли вообще код этой библиотеки в итоговый бинарь. SCA, построенный на заголовках, систематически ошибается — и пропусками, и ложными срабатываниями.

Эта заметка задаёт базовую дисциплину всего модуля: заголовок — это видимость интерфейса на этапе компиляции, а зависимость — факт линковки кода на этапе сборки. Это разные слои, и состав определяется вторым, а не первым.

Как это проявляется

Расхождение «заголовки против линковки» видно при сопоставлении исходников и реальной команды сборки:

# заголовки, которые видит компилятор
$ grep -rhoE '#include <[^>]+>' src/ | sort -u
#include <nlohmann/json.hpp>     # header-only: линковки НЕ будет
#include <openssl/ssl.h>         # есть и заголовок, и -lssl
#include <zlib.h>                # заголовок есть, а -lz в линковке НЕТ

# что реально передано линкеру
$ grep -oE '\-l[:]?[A-Za-z0-9._+-]+' build.log | sort -u
-lcrypto
-lssl

Здесь три разных случая в одном проекте: nlohmann/json — header-only, кода для линковки нет вообще; OpenSSL — заголовок и -lssl совпали, это настоящая зависимость; zlib — заголовок остался от старого кода, но -lz в линковке нет, то есть фактической зависимости нет (мёртвый include). Список по #include и список по -l не совпали ни в одну сторону.

Ошибка в обе стороны Header-only (Eigen, nlohmann/json, многие Boost): зависимость реальна, но в линковке её нет — состав по -l её пропустит, а по #include поймает. Мёртвый include: заголовок остался в коде, но соответствующего -l нет — состав по #include придумает несуществующую зависимость. Ни один из списков по отдельности не верен.

Слепая зона или приём

Слепая зона — header-only библиотеки: настоящая зависимость, которая не оставляет следа ни в DT_NEEDED, ни в строке линковки, потому что весь её код инлайнится из заголовков в объектные файлы. Поймать её можно только сопоставлением #include с известными header-only библиотеками — ни ldd, ни readelf её не покажут (это смежно со статической линковкой: код вшит, отдельной сущности нет).

Приём — не приравнивать #include к зависимости, а пересекать два независимых источника: заголовки из исходников (через compile_commands.json, см. компиляция и линковка) и реальные строки линковки. Совпадение по обоим — высокий confidence. Только заголовок без -l — либо header-only (реальная зависимость), либо мёртвый include (не зависимость): требует ручного разбора, а не автоматического вывода.

Что делать на проекте

  1. Не строить состав по grep #include — это даёт ложные срабатывания (мёртвые include) и пропуски (header-only).
  2. Базой состава делать реальные строки линковки (-l/-L из build.log), а заголовки использовать как перекрёстную проверку.
  3. Случай «заголовок есть, -l нет» разбирать вручную: header-only (внести как компонент) или мёртвый include (отбросить).
  4. Вести список известных header-only библиотек проекта — иначе они систематически выпадают из SBOM.
  5. Заголовок без линковки и без версии в коде заносить в реестр неопознанных, а не игнорировать.
  6. В отчёте различать уровни доказательства: «совпало заголовок + линковка» и «только заголовок» — это разный confidence записи.

Связь