Skip to content

Namespace-scope variables with dynamic initializers get wrong values when defined in separate transactions (cling 1.2) #567

Description

@rokabytedev

Edit (2026-08-17): the mechanism in my first follow-up comment was wrong, and Observation 3 below is inaccurate about const. See the correction comment — the root cause appears to be a __cxx_global_var_init symbol-name collision, and it looks already fixed in 1.3+.

Summary

When two or more namespace-scope variables with dynamic (runtime) initializers are defined in separate inputs, the values read back afterwards are wrong: the first variable holds the value produced by the last initializer, and every subsequent variable reads back as 0.

The initializers themselves all run — a side-effect counter shows exactly the expected number of calls — so the storage that later inputs read does not appear to be the storage the initializers wrote to.

Defining the same variables within a single input is correct, which gives a clean A/B. Upstream clang-repl at the same LLVM version (18.1.8) does not reproduce it.

Reproducer

Enter one line at a time at the cling prompt:

#include <cstdio>
long long n = 0;
long long b(){ return ++n; }
long long P = b();
long long Q = b();
printf("P=%lld Q=%lld n=%lld\n", P, Q, n);
Expected P=1 Q=2 n=2
Actual P=2 Q=0 n=2

(Program output goes to stderr, so when scripting this use cling 2>&1 <<'EOF' ... EOF.)

Observations

1. Control — the same definitions in a single input are correct.

long long P = b(); long long Q = b(); printf("P=%lld Q=%lld n=%lld\n", P, Q, n);

P=1 Q=2 n=2

2. It scales with the number of definitions. Three definitions, each its own input:

P=3 Q=0 R=0 n=3 (expected 1 2 3)

3. Not specific to auto, to const, or to any library type. Plain int with explicit types, still one definition per input:

#include <cstdio>
int m = 0;
int f(){ return ++m; }
int A = f();
int B = f();
printf("A=%d B=%d m=%d\n", A, B, m);

A=2 B=0 m=2 (expected 1 2 2)

4. Does not reproduce with upstream clang-repl at the same LLVM/Clang version cling 1.2 is built on, in the same container with the same libraries and the identical input:

cling 1.2         →  P=2 Q=0 n=2   (wrong)
clang-repl 18.1.8 →  P=1 Q=2 n=2   (correct)

5. Block-scope and function-local declarations are unaffected. In the same session, wrapping the same code in { ... } or in a function gives correct results, so this looks specific to namespace scope across separate transactions.

Practical impact

The most likely way to run into this is the ordinary "take two timestamps and subtract" idiom, which is natural to type as two separate lines:

#include <chrono>
#include <cstdio>
auto t0 = std::chrono::steady_clock::now();
auto t1 = std::chrono::steady_clock::now();
printf("t0=%lld t1=%lld\n", (long long)t0.time_since_epoch().count(), (long long)t1.time_since_epoch().count());

t0=1196998825664793 t1=0

so t1 - t0 silently yields a large negative duration. There is no diagnostic, and the failure is easy to misattribute to the timing code itself.

Environment

cling 1.2                       (conda-forge, cling-1.2-h48f18f5_1)
clang / LLVM 18.1.8             (conda-forge, cling_1.2 build)
Debian GNU/Linux 13 (trixie), x86_64

Reproduced identically in two environments that differ only in the C++ runtime (libstdcxx 14.1.0 and 16.1.0), so the behaviour does not appear to depend on the standard library version.

Caveat

Both environments print this at startup:

Warning in cling::IncrementalParser::CheckABICompatibility():
  Possible C++ standard library mismatch, compiled with __GLIBCXX__ '20240521'
  Extraction of runtime standard library version was: '20250605'

I could not get rid of it with the conda-forge packages. I don't think it explains the behaviour, because (a) the reproducer uses no C++ standard library types at all, (b) two environments with different libstdcxx versions produce identical results, and (c) function-scope declarations in the same session are correct. Flagging it in case it is relevant.

Tested by piping to the cling binary; I have not verified this against xeus-cling / Jupyter.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions