Add a script to list unintended symbols in libcrypto. A symbol is for now defined as unintended if it's not declared in a public header file, not namespaced, and likely seen by the linker. This will be used as a basis of symbol/namespacing cleanup; however it may be the case that not all symbols can be fixed easily. Bug: 42220000 Change-Id: Ic8daebeaba1223110b0a7c20b65bceb79890558d Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/85531 Commit-Queue: Rudolf Polzer <rpolzer@google.com> Auto-Submit: Rudolf Polzer <rpolzer@google.com> Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/list_unintended_exported_symbols.sh b/util/list_unintended_exported_symbols.sh new file mode 100755 index 0000000..302b7b3 --- /dev/null +++ b/util/list_unintended_exported_symbols.sh
@@ -0,0 +1,222 @@ +#!/bin/sh +# +# Copyright (c) 2025 The BoringSSL Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Audit source code for identifiers that do not match symbol prefixing +# guidelines. +# +# Note that this tool is a prototype; once prefixing public symbols has been +# implemented, a much simpler tool can be created and integrated directly into +# the build process rather than running as a separate audit with bespoke cache +# files. +# +# TODO(crbug.com/42220000): Eventually port it into the build process. + +set -e +set -o pipefail + +include_files() { + for file in "$@"; do + echo "#include <${file}>" + done +} + +source_to_ast() { + clang++ \ + -Iinclude \ + -Ithird_party/benchmark/include \ + -Ithird_party/googletest/googlemock/include \ + -Ithird_party/googletest/googletest/include \ + -fsyntax-only \ + -Xclang -ast-dump=json \ + "$@" +} + +ast_to_identifiers() { + go run util/extract_identifiers_clang_json.go "$@" +} + +c_source_to_identifiers() { + source_to_ast -x c -std=c17 "$@" | ast_to_identifiers --language=C +} + +cc_source_to_identifiers() { + source_to_ast -x c++ -std=c++17 "$@" | ast_to_identifiers --language=C++ +} + +lib_sources() { + jq -r ' + [.bcm, .crypto, .decrepit] | + .[] | + (.internal_hdrs + .srcs) | + .[]? + ' gen/sources.json |\ + perl -pe '$_ = "" if /\.inc$/m and not /\.cc\.inc$/m' |\ + grep -vxF 'crypto/curve25519/curve25519_tables.h' |\ + grep -vxF 'crypto/fipsmodule/ec/builtin_curves.h' |\ + grep -vxF 'crypto/fipsmodule/ec/p256-nistz-table.h' |\ + grep -vxF 'crypto/fipsmodule/ec/p256_table.h' |\ + grep -vxF 'crypto/obj/obj_dat.h' |\ + grep -vE '^third_party/.*' |\ + sort -u +} + +public_c_includes() { + jq -r ' + [.bcm, .crypto, .decrepit] | + .[] | + .hdrs | + .[]? + ' gen/sources.json |\ + sed -e 's,^include/,,' +} + +public_cc_includes() { + # Note: using |hdrs| of _all_ modules. The assumption is that all + # |hdrs| are public. + jq -r ' + .[] | + .hdrs | + .[]? + ' gen/sources.json |\ + sed -e 's,^include/,,' +} + +set_difference() { + { + sort -u < "$1" + shift + for f in "$@"; do + cat "${f}" + cat "${f}" + done + } | sort | uniq -u +} + +filter_bssl() { + # Whatever is in the bssl namespace is OK. + grep -vE ' bssl::' || true +} + +filter_no_symbols() { + # Whatever is static or an enumerator never becomes part of a symbol. + grep -vE "^(static|enumerator) " || true +} + +filter_expected_symbols() { + # Ignore types for now (they can become _part_ of a symbol name, + # but are unlikely to clash). + grep -vE "^(class|enum|struct|typedef|union|using) " || true +} + +filter_stl() { + # These symbols are often declared to support the STL and are benign, + # as they have C++ linkage and very specific arguments and thus + # non-conflicting mangled names. + grep -vE 'extern "C\+\+" function (begin|end);' || true +} + +filter_enum() { + # Some enums are declared by public headers only in C++. + # This is intended. + grep -vE "^enum " || true +} + +filter_difference() { + filter_bssl |\ + filter_no_symbols |\ + filter_expected_symbols |\ + filter_stl +} + +fix_c_cc_include_deltas() { + # OPENSSL_INLINE behaves differently in C and C++. + sed -e 's,^static ,extern "C" ,g' |\ + filter_bssl |\ + filter_stl |\ + filter_enum |\ + sort -u +} + +echo >&2 'Indexing C++ includes...' +include_files $(public_cc_includes) | cc_source_to_identifiers - > include.cc.ids +echo >&2 'Indexing C includes...' +include_files $(public_c_includes) | c_source_to_identifiers - > include.c.ids +echo >&2 'Indexing C includes as C++...' +include_files $(public_c_includes) | cc_source_to_identifiers - > include.c_as_cc.ids + +# Check that the headers behave the same if included by C and C++ files, other +# than for expected diffs. +echo >&2 'Comparing C includes across including language...' +fix_c_cc_include_deltas < include.c.ids > include.c.common.ids +fix_c_cc_include_deltas < include.c_as_cc.ids > include.c_as_cc.common.ids +diff -u include.c.common.ids include.c_as_cc.common.ids >&2 + +# Check that no source file defines any public symbols that are not in the +# public headers, namespaced or otherwise OK'd. +max_jobs=16 +set -- +wait_jobs() { + wait + for f in "$@"; do + [ -f "${f}" ] + done +} +for file in $(lib_sources); do + if ! [ -f "${file}.ids" ]; then + echo >&2 "Indexing ${file} (new)..." + elif [ "${file}" -nt "${file}.ids" ]; then + echo >&2 "Indexing ${file} (changed)..." + rm -f "${file}.ids" + else + perl -nE 'm!// (\S+):! and say $1' "${file}.ids" |\ + sort -u |\ + while read -r dep; do + if [ "${dep}" -nt "${file}.ids" ]; then + echo >&2 "Indexing ${file} (dependency ${dep} changed)..." + rm -f "${file}.ids" + break + fi + done + if [ -f "${file}.ids" ]; then + continue + fi + fi + # NOTE: It'd be nice to only recompute ${file}.ids if ${file} _or_ one + # of its dependencies changed. + { + case "$file" in + *.c) + c_source_to_identifiers "${file}" + ;; + **) + # This includes headers too. + cc_source_to_identifiers "${file}" + ;; + esac > "${file}.ids.new" + mv "${file}.ids.new" "${file}.ids" + } & + set -- "$@" "${file}.ids" + if [ $# -ge ${max_jobs} ]; then + wait_jobs "$@" + set -- + fi +done +wait_jobs "$@" + +echo >&2 'Comparing symbols...' +for file in $(lib_sources); do + set_difference "${file}.ids" include.cc.ids | filter_difference +done | sort -u | { ! grep .; }