Skip to content

Doxygen might generate invalid tag file, breaking downstream project documentation (no uniquely matching class member found for) #11569

@romintomasetti

Description

@romintomasetti

Describe the bug

I have a project A that generates a tag file, that my project B consumes.

The documentation step of project A goes fine. It uploads its tag file.

Then, project B picks up the tag file of project A and builds its own documentation. At that point, I'm seeing the following issue:

error: no uniquely matching class member found for

I've been able to reproduce it, see below.

To Reproduce

Here is a self-contained reproducer using `Docker`.

You can build it with:

docker buildx build --progress plain --tag my-reproducer-image - < reproducer.dockerfile

and the build will fail, demonstrating the issue.

FROM ubuntu:24.04

# Install system requirements.
RUN <<EOF
    set -ex

    apt update
    
    apt --yes --no-install-recommends install gcc g++ python3 cmake make git flex bison ca-certificates
EOF

# Install custom Doxygen version from GitHub.
ARG DOXYGEN_VERSION=1.13.2

RUN <<EOF
    git clone --depth=1 --branch Release_$(echo ${DOXYGEN_VERSION} | tr . _) https://github.com/doxygen/doxygen doxygen

    cd doxygen

    cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/opt/doxygen-${DOXYGEN_VERSION}

    cmake --build build -j4 --target install
EOF

ENV Doxygen_ROOT=/opt/doxygen-${DOXYGEN_VERSION}

# This is the problematic file.
COPY <<EOF generate-invalid-tag-file/MyLibrary.hpp
    #ifndef MYLIBRARY_HPP
    #define MYLIBRARY_HPP

    #include <iostream>
    #include <memory>

    namespace mylib
    {
        //! Default implementation of a helper @c struct.
        template <typename T>
        struct MyHelper
        {
            //! Say hello to your developer.
            void say_hello(const T value) const {
                printf("Default %s %f\\n", __PRETTY_FUNCTION__, value);
            }
        };

        //! Specialization for @c int.
        template <>
        struct MyHelper<int>
        {
            //! Deletor for @ref member.
            struct Deletor
            {
                //! This is some documentation.
                void operator()(const int*) const {}
            };

            void say_hello(const int value) const {
                printf("Specialized %s %d\\n", __PRETTY_FUNCTION__, value);
            }

            using impl_event_t    = int;
            using event_storage_t = std::unique_ptr<impl_event_t, Deletor>;

            event_storage_t member; //!< Member documentation.
        };

        //! This class call operator interfers with the above.
        struct Other
        {
            template <typename T>
            void operator()(const T&) const {
                printf("%s\\n", __PRETTY_FUNCTION__);
            }
        };

        //! This class call operator also interfers with the above.
        struct YetAnOther
        {
            template <typename T>
            void operator()(const T&) const {
                printf("%s\\n", __PRETTY_FUNCTION__);
            }
        };

    } // namespace mylib

    #endif // MYLIBRARY_HPP
EOF

# A simple test to check it's valid C++.
COPY <<EOF generate-invalid-tag-file/test_mylib.cpp
    #include "MyLibrary.hpp"

    int main()
    {
        mylib::MyHelper<int   >{}.say_hello(42);
        mylib::MyHelper<double>{}.say_hello(42.);
        mylib::Other           {}.operator()<char>(char(42));
    }
EOF

# This is a project that will generate an invalid tag file.
COPY <<EOF generate-invalid-tag-file/CMakeLists.txt
    cmake_minimum_required(VERSION 3.25)

    project(GenerateInvalidTagFile LANGUAGES CXX)

    add_executable(test)
    target_sources(test PRIVATE test_mylib.cpp)

    find_package(Doxygen ${DOXYGEN_VERSION} REQUIRED)

    set(DOXYGEN_EXTRACT_ALL YES)
    set(DOXYGEN_WARN_AS_ERROR YES)
    set(DOXYGEN_WARN_IF_UNDOCUMENTED NO)
    set(DOXYGEN_GENERATE_TAGFILE "$\{CMAKE_SOURCE_DIR\}/invalid-tag-file.tag")
    doxygen_add_docs(docs MyLibrary.hpp)
EOF

RUN <<EOF
    set -ex

    cd generate-invalid-tag-file

    cmake -S . -B build

    cmake --build build --target=test

    ./build/test

    cmake --build build --target=docs

    test -f invalid-tag-file.tag
EOF

# This is a project that will try to consume the invalid tag file.
COPY <<EOF consume-invalid-tag-file/test_empty.hpp
//! This does not matter.
void empty();
EOF

COPY <<EOF consume-invalid-tag-file/CMakeLists.txt
    cmake_minimum_required(VERSION 3.25)

    project(ConsumeInvalidTagFile LANGUAGES CXX)

    find_package(Doxygen ${DOXYGEN_VERSION} REQUIRED)

    set(DOXYGEN_EXTRACT_ALL YES)
    set(DOXYGEN_WARN_AS_ERROR YES)
    set(DOXYGEN_WARN_IF_UNDOCUMENTED NO)
    set(DOXYGEN_TAGFILES "/generate-invalid-tag-file/invalid-tag-file.tag")
    if(NOT EXISTS $\{DOXYGEN_TAGFILES\})
        message(FATAL_ERROR "The tag file $\{DOXYGEN_TAGFILES\} path is not valid.")
    endif()

    doxygen_add_docs(docs test_empty.hpp)
EOF

RUN <<EOF
    set -ex

    cd consume-invalid-tag-file

    cmake -S . -B build

    cmake --build build --target=docs
EOF

Here is the relevant output:

#18 0.464 + cmake --build build --target=docs
#18 0.519 [100%] Generate API documentation for docs
#18 0.533 /generate-invalid-tag-file/invalid-tag-file.tag:17: error: no uniquely matching class member found for 
#18 0.533   void mylib::MyHelper< int >::Deletor::operator()(const int *) const
#18 0.533 Possible candidates:
#18 0.533   'void mylib::Other::operator()(const T &) const' at line 87 of file /generate-invalid-tag-file/invalid-tag-file.tag
#18 0.533   'void mylib::YetAnOther::operator()(const T &) const' at line 98 of file /generate-invalid-tag-file/invalid-tag-file.tag (warning treated as error, aborting now)
#18 0.533 Doxygen version used: 1.13.2 (26342b775ea25e6fefb53220926b20702c56fcb3)
#18 0.533 Searching for include files...
#18 0.533 Searching for example files...

Expected behavior

The generator tag file should be valid, and reusable in downstream projects.

Version

Using Doxygen 1.13.2.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions