Skip to content

C++: class with export macro + inheritance mislabeled as kind=function in header #946

Closed

Description

@spwlyzx

Problem

A C++ class declaration in a header file that uses an export macro (e.g. __attribute__((visibility("default")))) and inherits from a base class is indexed as kind=function instead of kind=class. Its qualified_name has no :: scope, and its line range covers the entire class body.

Minimal Reproduction

Create provider.h:

#pragma once
#include <cstdint>

#define MAPCORE_EXPORT __attribute__((visibility("default")))

class DataProvider {
public:
    virtual ~DataProvider() = default;
    virtual bool Request(void* param) = 0;
};

class MapDataLoader;
namespace mirror { class Framework; }

class MAPCORE_EXPORT LocalDataProvider : public DataProvider
{
public:
    LocalDataProvider(int32_t dataType, MapDataLoader* loader, mirror::Framework* framework);
    virtual ~LocalDataProvider();

    static void* ConvertTileBlobToData(int32_t dataType,
                                       mirror::Framework* framework,
                                       const void* blob,
                                       bool deepCopy = false);
    virtual bool Request(void* param) override;

private:
    int32_t m_dataType{0};
};
codegraph init
sqlite3 .codegraph/codegraph.db "SELECT kind, name, qualified_name, start_line, end_line FROM nodes WHERE name = 'LocalDataProvider'"

Actual:

function|LocalDataProvider|LocalDataProvider|22|39

Expected:

class|LocalDataProvider|LocalDataProvider|22|39

Note: DataProvider (no export macro) in the same file is correctly indexed as class. Removing MAPCORE_EXPORT from LocalDataProvider's declaration makes it index correctly as class.

Trigger Conditions

The mislabeling occurs when both conditions are met:

  1. An export/visibility macro precedes the class name: class MACRO ClassName
  2. The class inherits from a base: : public BaseClass

A class with only one of these (macro but no inheritance, or inheritance but no macro) is indexed correctly.

Impact

  • codegraph impact and codegraph callers may include this phantom "function" node in call-graph traversal results
  • SQLite WITH RECURSIVE queries for blast-radius analysis pick up the node as a false caller
  • Graph statistics (node kind distribution) are skewed

Workaround

-- Exclude header-file "function" nodes without :: in qualified_name
AND NOT (kind = 'function' AND file_path LIKE '%.h' AND qualified_name NOT LIKE '%::%')

Environment

  • CodeGraph 1.0.1
  • macOS arm64
  • C++ codebase (~2500 .h/.cpp files)

Activity

  1. changed the title [-]C++: class declaration in header mislabeled as kind=function when constructor is the first public member[/-] [+]C++: class with export macro + inheritance mislabeled as kind=function in header[/+] on Jun 22, 2026
  2. colbymchenry commented on Jun 22, 2026

    @colbymchenry
    Owner

    Fixed in #948 (merged to main) — thanks for the clean minimal reproduction, it made the root cause easy to pin down.

    Confirmed by dumping the parse tree: tree-sitter doesn't expand macros, so class MAPCORE_EXPORT LocalDataProvider : public DataProvider is read as an elaborated type specifier (class MAPCORE_EXPORT, the macro mistaken for the type) followed by a declarator and a body — i.e. the whole declaration becomes a function_definition named after the class. CodeGraph now detects that misparse structurally and drops the phantom node, so it no longer pollutes codegraph callers / codegraph impact / blast-radius or skews the kind counts.

    One honest caveat on scope: the fix removes the bogus function rather than re-indexing LocalDataProvider as a class. The same misparse also mangles the class body (members get scattered into error/expression fragments), so the declaration isn't recoverable as a clean class without genuine macro-expansion awareness — which tree-sitter alone doesn't have. Removing the misleading node is the honest fix here; the symbol simply won't appear (rather than appearing wrong).

    Also worth noting: the misparse is triggered by the leading macro alone — class MACRO Name { … } misparses even without a base class — so the fix covers that case too (and the struct variant), not just the macro+inheritance combination. Ships in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions