View Issue Details

IDProjectCategoryView StatusLast Update
0010184ardourotherpublic2026-02-17 02:17
Reporteru1f992 Assigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
PlatformUbuntuOSLinuxOS Version(any)
Product Version9.0 
Summary0010184: Modified taglib source not found
DescriptionThe nightly build dependencies page (https://nightly.ardour.org/list.php#build_deps) links to `http://taglib.github.io/releases/taglib-1.9.1.tar.gz` as the taglib source.

I built taglib from this source, but the resulting `libtag.so.1` does not match the one in the Ardour 9.0 Linux x86_64 installer. The installer version contains symbols that are not present in a 1.9.1 build, for example:

- `TagLib::String::MyPrivateString` (upstream 1.9.1 uses `StringPrivate`)
- `TagLib::ListPrivateBase` (only found on the taglib `experimental` branch, not in any release)
- `vsnprintf@GLIBC_2.2.5` (not imported by a 1.9.1 build)

The installer binary has 2,876 dynamic symbols vs 2,821 from my 1.9.1 build (+55 symbols). I was not able to find a matching source: `MyPrivateString` does not appear anywhere in the taglib git history, and the combination of `ListPrivateBase` with `RefCounterOld` does not correspond to any single commit in the repository.

Is the modified taglib source or patch available somewhere? I could not find it on the nightly page, in `ardour.org/files/deps/`, or in `tools/misc-patches/` on the `2.0-ongoing` branch.
Steps To Reproduce1. Download the Ardour 9.0 Linux x86_64 installer.
2. Extract `libtag.so.1` from the bundle.
3. Run: `nm -D libtag.so.1 | c++filt | grep MyPrivateString`. The symbol is present.
4. Build taglib 1.9.1 from `http://taglib.github.io/releases/taglib-1.9.1.tar.gz`.
5. Run the same command on the self-built `libtag.so.1`. The symbol is absent.
Additional InformationTaglib is dual-licensed under LGPL 2.1 and MPL 1.1.
TagsNo tags attached.

Activities

u1f992

2026-02-15 16:46

reporter   ~0029883

I should clarify that my mention of `tools/misc-patches/` on the `2.0-ongoing` branch was because I conflated it with issue 0010185, which I filed around the same time. Regardless, I have since searched the entire git history of the Ardour repository (all branches and all tags) and found no taglib patch file and no modified taglib source. The `libs/taglib/` directory that existed in the tree from 2008 to 2014 (removed in commit 0a2a6aaabb) contained unmodified upstream source (`StringPrivate`, not `MyPrivateString`).

I also considered whether the symbol differences could be explained purely by build configuration, without source modification, for example by passing `-DStringPrivate=MyPrivateString` to the compiler. This successfully renames the class in the symbol table, and it compiles cleanly. However, this alone cannot account for the full set of differences.

Specifically, the bundled `libtag.so.1` contains vtables and RTTI typeinfo for `ListPrivate<T*>` template instantiations (e.g., `List<ID3v2::Frame*>::ListPrivate`). In the upstream 1.9.1 source, the inheritance chain is `ListPrivate<T*>` -> `ListPrivateBase` -> `RefCounterOld`, and none of these classes have virtual functions. `RefCounterOld`, unlike `RefCounter`, lacks a virtual destructor. Without virtual functions, the compiler will not emit vtables or polymorphic typeinfo for these classes, regardless of optimization level or visibility settings.

This means the bundled library reflects source-level modifications beyond what any combination of `-D` flags or compiler options can reproduce from the upstream 1.9.1 tarball linked on the nightly builds page.

x42

2026-02-16 15:47

administrator   ~0029885

macOS has a flat namespace, and NI machine already defines "StringPrivate", so something has to give to avoid conflicting symbols.
on GNU/Linux this is not relevant since there is no global flat namesapce; as for the other differences, we compile taglib with "-O3 -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -std=c++11".

u1f992

2026-02-17 02:17

reporter   ~0029889

Thank you for the explanation regarding the macOS flat namespace. The motivation for renaming `StringPrivate` is now clear.

I attempted to reproduce the bundled libtag.so.1 from the taglib 1.9.1 tarball listed on the nightly builds page, using the flags you mentioned, but the resulting binary did not match the bundled one; 53 dynamic symbols remain unaccounted for (details in the appendix).

In 0010185, Paul mentioned that there are numerous other patches applied as part of the dependency stack build, and that making them publicly available is being considered. The patch described in the appendix may be among them. If so, publishing it would also address the source-availability question.
appendix.md (6,860 bytes)   
## Appendix: Reproduction details

### A1. Setup and reproduction

Build environment (Dockerfile):

```dockerfile
# GCC 10 is used because the bundled library's __cxa_pure_virtual
# binding (U, not w) matches GCC 10 but not GCC 11+.  The libstdc++
# imports (std::ios_base::Init::Init/~Init) are consistent with any
# GCC version prior to 13.
#
# CMake 2.8.12.2 also requires GCC 10 or earlier to compile
# (it fails with GCC 11+).
FROM ubuntu:24.04
RUN apt-get update -qq \
 && apt-get install -qq -y gcc-10 g++-10 zlib1g-dev binutils make wget > /dev/null
RUN cd /tmp \
 && wget -q https://cmake.org/files/v2.8/cmake-2.8.12.2.tar.gz \
 && tar xzf cmake-2.8.12.2.tar.gz && cd cmake-2.8.12.2 \
 && CC=gcc-10 CXX=g++-10 ./bootstrap --prefix=/opt/cmake > /dev/null 2>&1 \
 && make -j$(nproc) > /dev/null 2>&1 \
 && make install > /dev/null 2>&1
ENV PATH="/opt/cmake/bin:$PATH"
RUN cd /tmp \
 && wget -q https://taglib.org/releases/taglib-1.9.1.tar.gz \
 && tar xzf taglib-1.9.1.tar.gz
```

Build and run:

```
docker build -t taglib-env .
docker run --rm -it taglib-env
```

Inside the container, copy the source tree and apply the one-line patch to the copy:

```sh
cd /tmp
cp -a taglib-1.9.1 taglib-1.9.1-patched
sed -i 's/~ListPrivate() {/virtual ~ListPrivate() {/' \
    taglib-1.9.1-patched/taglib/toolkit/tlist.tcc
```

Build both with the same flags.
The flag `-DStringPrivate=MyPrivateString` reproduces the class rename; whether the actual build uses this flag or a source patch is unknown, but the effect on the dynamic symbol table is the same.
The bundled library's dynamic symbol table includes `vsnprintf@GLIBC_2.2.5`, which is called only from `debug()` and `debugData()` in tdebug.cpp. This indicates the library was built without `-DNDEBUG`.

```sh
FLAGS="-O3 -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \
  -std=c++11 -DStringPrivate=MyPrivateString \
  -fno-stack-protector -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"

# (A) Unmodified
mkdir build-unmod && cd build-unmod
cmake /tmp/taglib-1.9.1 -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_CXX_FLAGS="$FLAGS"
make -j$(nproc)
cd /tmp

# (B) Patched
mkdir build-patched && cd build-patched
cmake /tmp/taglib-1.9.1-patched -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_CXX_FLAGS="$FLAGS"
make -j$(nproc)
cd /tmp
```

Compare dynamic symbols (within the same build, addresses are comparable):

```sh
nm -D build-unmod/taglib/libtag.so.1 | c++filt | sort > sym-unmod.txt
nm -D build-patched/taglib/libtag.so.1 | c++filt | sort > sym-patched.txt
diff sym-unmod.txt sym-patched.txt
```

To compare against the bundled library, extract `libtag.so.1` from the Ardour 9.0 x86_64 Linux installer (`Ardour-9.0.0-x86_64.run`, downloaded 2026-02-15, SHA256: `713f11881a502585fffe3eade58ed41e0fe290983eae794f328012876767b4d1`):

```sh
./Ardour-9.0.0-x86_64.run --noexec --target ardour-extracted
nm -D ardour-extracted/Ardour_x86_64-9.0.0/lib/libtag.so.1 | c++filt | sort > sym-bundled.txt
```

Cross-build comparison requires stripping addresses (different builds produce different layouts):

```sh
sed 's/^[0-9a-f]* //' sym-patched.txt | sort > sym-patched-names.txt
sed 's/^[0-9a-f]* //' sym-bundled.txt | sort > sym-bundled-names.txt
diff sym-patched-names.txt sym-bundled-names.txt
```

### A2. Results

                          (A) Unmodified   (B) Patched   Ardour bundled
  Total symbols                     2819          2872             2872
  ListPrivate<T*> vtables              0             8                8
  ListPrivate<T*> typeinfo             0             9                9
  ListPrivateBase  typeinfo            0             2                2
  RefCounterOld    typeinfo            0             2                2
  MyPrivateString  vtable/ti           2             2                2

Build (A) is missing 53 symbols present in the bundled library (2872 - 2819).
Build (B), with only the one-line patch, produces a dynamic symbol table identical to the bundled library's: all 2872 symbols match in name and binding type.

### A3. Explanation

The StringPrivate rename produces a vtable for MyPrivateString because String::StringPrivate inherits from RefCounter, which has a virtual destructor. This is expected and unrelated to the `ListPrivate` patch.

However, the vtables for ListPrivate<T*> cannot be produced by any combination of compiler flags. The inheritance chain is:

  RefCounterOld        (no virtual functions, no destructor declared)
    └─ ListPrivateBase (no virtual functions)
        └─ ListPrivate<TP*>  (destructor is non-virtual in upstream)

Per the C++ object model, no vtable or polymorphic RTTI is emitted without at least one virtual function in the hierarchy, regardless of optimization level, visibility settings, or C++ standard version.

Adding `virtual` to `~ListPrivate()` in the pointer-type partial specialization makes that class polymorphic. This produces exactly the 8 vtable entries, 9 typeinfo entries, and 2 `ListPrivateBase` + 2 `RefCounterOld` base-class typeinfo entries seen in the bundled library, matching the total of 2872 symbols.

### A4. Cross-check against taglib Git history

I also checked whether any commit in the taglib repository could produce vtables for `ListPrivate<T*>` while `RefCounterOld` is part of the inheritance chain. Per the C++ object model, this requires at least one virtual function somewhere in the `RefCounterOld -> ListPrivateBase -> ListPrivate<T*>` hierarchy (not necessarily in `ListPrivate` itself).

I scanned all 3,015 commits at https://github.com/taglib/taglib (as of 2026-02-17). Of those, 1,112 commits contain `RefCounterOld` in `trefcounter.h`, spanning 12 unique file-state combinations. Each was checked for `virtual` or `override` in any of the three classes:

  RefCounterOld (5 unique versions):
    No virtual functions or virtual destructors in any version.

  ListPrivateBase (6 unique versions, when inheriting from RefCounterOld):
    No virtual functions, no virtual or override keywords.

  ListPrivate<T*> (6 unique versions, same commits):
    No virtual functions, no virtual or override keywords.

Additionally, there is no commit where `RefCounterOld` exists and `ListPrivateBase` inherits from `RefCounter` (which does have a virtual destructor). When `RefCounterOld` is present, `ListPrivateBase` always inherits from `RefCounterOld`.

For completeness: between commits 9867bc94 and a1bdb017 (July 2023), `~ListPrivate()` was marked `override`, but in those commits `ListPrivateBase` inherited from `RefCounter` (not `RefCounterOld`), so `RefCounterOld` was not in the inheritance chain and its typeinfo would not be emitted.

No commit in the repository produces a polymorphic `ListPrivate<T*>` hierarchy rooted in `RefCounterOld`. The bundled binary's symbol table cannot be reproduced from any upstream commit or tagged release.
appendix.md (6,860 bytes)   

Issue History

Date Modified Username Field Change
2026-02-14 13:48 u1f992 New Issue
2026-02-15 16:46 u1f992 Note Added: 0029883
2026-02-16 15:47 x42 Note Added: 0029885
2026-02-17 02:17 u1f992 Note Added: 0029889
2026-02-17 02:17 u1f992 File Added: appendix.md