Collect C++ lcov coverage if runtime object not in runfiles

Before this commit, collecting C++ coverage in lcov format would fail
at the llvm-cov export step if a shared library listed in the
runtime_objects_list.txt was not contained in the runfiles of the top-
level target. This can happen e.g. if a cc_library depends on a
java_binary that has a cc_binary shared library in its resources.

This is fixed by not including objects that don't exist at runtime
in the llvm-cov invocation.

Fixes #15121.

Closes #15118.

PiperOrigin-RevId: 442799461
diff --git a/src/test/shell/bazel/bazel_coverage_cc_test_llvm.sh b/src/test/shell/bazel/bazel_coverage_cc_test_llvm.sh
index 07cc3ff..be3b1ed 100755
--- a/src/test/shell/bazel/bazel_coverage_cc_test_llvm.sh
+++ b/src/test/shell/bazel/bazel_coverage_cc_test_llvm.sh
@@ -146,7 +146,77 @@
 LF:7
 end_of_record"
 
-  assert_equals "$(cat $(get_coverage_file_path_from_test_log))" "$expected_result"
+  assert_equals "$expected_result" "$(cat $(get_coverage_file_path_from_test_log))"
 }
 
+function test_cc_test_with_runtime_objects_not_in_runfiles() {
+  local -r llvm_profdata="/usr/bin/llvm-profdata-9"
+  if [[ ! -x ${llvm_profdata} ]]; then
+    return
+  fi
+
+  local -r clang="/usr/bin/clang-9"
+  if [[ ! -x ${clang} ]]; then
+    return
+  fi
+
+  local -r llvm_cov="/usr/bin/llvm-cov-9"
+  if [[ ! -x ${llvm_cov} ]]; then
+    return
+  fi
+
+  cat << EOF > BUILD
+cc_test(
+    name = "main",
+    srcs = ["main.cpp"],
+    data = [":jar"],
+)
+
+java_binary(
+    name = "jar",
+    resources = [":shared_lib"],
+    create_executable = False,
+)
+
+cc_binary(
+    name = "shared_lib",
+    linkshared = True,
+)
+EOF
+
+  cat << EOF > main.cpp
+#include <iostream>
+
+int main(int argc, char const *argv[])
+{
+  if (argc < 5) {
+    std::cout << "Hello World!" << std::endl;
+  }
+}
+EOF
+
+
+  BAZEL_USE_LLVM_NATIVE_COVERAGE=1 GCOV=$llvm_profdata CC=$clang \
+    BAZEL_LLVM_COV=$llvm_cov bazel coverage --experimental_generate_llvm_lcov \
+      --test_output=all --instrument_test_targets \
+        //:main &>$TEST_log || fail "Coverage for //:main failed"
+
+  local expected_result="SF:main.cpp
+FN:4,main
+FNDA:1,main
+FNF:1
+FNH:1
+DA:4,1
+DA:5,1
+DA:6,1
+DA:7,1
+DA:8,1
+LH:5
+LF:5
+end_of_record"
+
+  assert_equals "$expected_result" "$(cat $(get_coverage_file_path_from_test_log))"
+}
+
+
 run_suite "test tests"
\ No newline at end of file
diff --git a/tools/test/collect_cc_coverage.sh b/tools/test/collect_cc_coverage.sh
index c339659..f532e9b 100755
--- a/tools/test/collect_cc_coverage.sh
+++ b/tools/test/collect_cc_coverage.sh
@@ -85,7 +85,9 @@
   while read -r line; do
     if [[ ${line: -24} == "runtime_objects_list.txt" ]]; then
       while read -r line_runtime_object; do
+        if [[ -e "${RUNFILES_DIR}/${TEST_WORKSPACE}/${line_runtime_object}" ]]; then
           object_param+=" -object ${RUNFILES_DIR}/${TEST_WORKSPACE}/${line_runtime_object}"
+        fi
       done < "${line}"
     fi
   done < "${COVERAGE_MANIFEST}"