|
| 1 | +#!/bin/bash |
| 2 | +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 3 | +# SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +set -eEuo pipefail |
| 6 | + |
| 7 | +echo "checking for symbol visibility issues" |
| 8 | + |
| 9 | +LIBRARY="${1}" |
| 10 | + |
| 11 | +echo "" |
| 12 | +echo "Checking exported symbols in '${LIBRARY}'" |
| 13 | +symbol_file="$(mktemp)" |
| 14 | +match_file="$(mktemp)" |
| 15 | +trap 'rm -f "${symbol_file}" "${match_file}"' EXIT |
| 16 | + |
| 17 | +# Ignore WEAK and UNIQUE symbols since UNIQUE symbols should be exported and |
| 18 | +# WEAK symbols may come from template instantiations. |
| 19 | +# Ignore symbols containing "_error" since these are likely exception types |
| 20 | +# and should be exported. |
| 21 | + |
| 22 | +readelf --dyn-syms --wide "${LIBRARY}" \ |
| 23 | + | awk '$7 != "UND" && $5 != "WEAK" && $5 != "UNIQUE"' \ |
| 24 | + | c++filt --no-params \ |
| 25 | + | awk '$0 !~ /_error/' \ |
| 26 | + > "${symbol_file}" |
| 27 | + |
| 28 | +patterns=( |
| 29 | + 'cub::' |
| 30 | + 'thrust::' |
| 31 | + 'raft::' |
| 32 | + 'rmm::' |
| 33 | + 'cuopt::mathematical_optimization::detail' |
| 34 | + 'cuopt::routing::detail' |
| 35 | + 'cuopt::detail' |
| 36 | + 'grpc::' |
| 37 | + 'google::protobuf' |
| 38 | + 'tbb::' |
| 39 | + 'absl::' |
| 40 | + 'dejavu::' |
| 41 | + 'papilo::' |
| 42 | + 'boost::' |
| 43 | +) |
| 44 | + |
| 45 | +failed=0 |
| 46 | + |
| 47 | +for pattern in "${patterns[@]}"; do |
| 48 | + echo "Checking for '${pattern}' symbols..." |
| 49 | + |
| 50 | + awk -v pattern="${pattern}" ' |
| 51 | + BEGIN { has_trailing_scope = (substr(pattern, length(pattern) - 1) == "::") } |
| 52 | + $1 ~ /^[0-9]+:/ { |
| 53 | + symbol = "" |
| 54 | + for (i = 8; i <= NF; ++i) { |
| 55 | + symbol = symbol (i == 8 ? "" : " ") $i |
| 56 | + } |
| 57 | +
|
| 58 | + sub(/<.*/, "", symbol) |
| 59 | + sub(/^.*[[:space:]](for|to)[[:space:]]+/, "", symbol) |
| 60 | +
|
| 61 | + if (has_trailing_scope) { |
| 62 | + matched = (index(symbol, pattern) == 1) |
| 63 | + } else { |
| 64 | + matched = (symbol == pattern || index(symbol, pattern "::") == 1) |
| 65 | + } |
| 66 | +
|
| 67 | + if (matched) { print } |
| 68 | + } |
| 69 | + ' "${symbol_file}" > "${match_file}" |
| 70 | + |
| 71 | + matches=$(awk 'END { print NR }' "${match_file}") |
| 72 | + if [[ "${matches}" -ne 0 ]]; then |
| 73 | + sed -n '1,20p' "${match_file}" |
| 74 | + echo "ERROR: Found exported symbols in ${LIBRARY} matching the pattern ${pattern}." |
| 75 | + echo "ERROR: Total matching symbols: ${matches}" |
| 76 | + failed=1 |
| 77 | + fi |
| 78 | +done |
| 79 | + |
| 80 | +# Required public API symbols that must stay exported. This is a small stability |
| 81 | +# anchor (core C API lifecycle entrypoints), not an exhaustive list: without it, |
| 82 | +# a library whose visibility was over-tightened so the public API is entirely |
| 83 | +# hidden would still pass the forbidden-symbol checks above while being unusable. |
| 84 | +# Keep this set minimal and limited to entrypoints guaranteed to exist. |
| 85 | +required_symbols=( |
| 86 | + cuOptReadProblem |
| 87 | + cuOptCreateProblem |
| 88 | + cuOptSolve |
| 89 | + cuOptDestroyProblem |
| 90 | +) |
| 91 | + |
| 92 | +exported_funcs="$(readelf --dyn-syms --wide "${LIBRARY}" | awk '$7 != "UND" && $4 == "FUNC" { print $8 }')" |
| 93 | + |
| 94 | +for sym in "${required_symbols[@]}"; do |
| 95 | + echo "Checking that required symbol '${sym}' is exported..." |
| 96 | + if ! grep -qxF "${sym}" <<< "${exported_funcs}"; then |
| 97 | + echo "ERROR: Required public API symbol '${sym}' is not exported from ${LIBRARY}." |
| 98 | + echo "ERROR: Symbol visibility may be over-restricted and hiding the public API." |
| 99 | + failed=1 |
| 100 | + fi |
| 101 | +done |
| 102 | + |
| 103 | +if [[ "${failed}" -ne 0 ]]; then |
| 104 | + exit 1 |
| 105 | +fi |
| 106 | + |
| 107 | +echo "No symbol visibility issues found in ${LIBRARY}" |
0 commit comments