Skip to content

Fix the harmful uses of #define private public - #7908

Closed
Critsium-xy wants to merge 6 commits into
deepmodeling:developfrom
Critsium-xy:fix/test-access-control-hack
Closed

Fix the harmful uses of #define private public#7908
Critsium-xy wants to merge 6 commits into
deepmodeling:developfrom
Critsium-xy:fix/test-access-control-hack

Conversation

@Critsium-xy

@Critsium-xy Critsium-xy commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Background

#define private public around an #include appears in 81 files in this tree
(111 occurrences). It is not a harmless test trick: it gives that translation unit
a different definition of the class than the rest of the library it links
against, which is an ODR violation, and it silently applies to every other header
pulled in by the same region.

This PR does not remove the pattern. It fixes the places where it is currently
doing damage, and removes the instances that do nothing at all.

What is fixed

1. The one occurrence in production code

source_io/module_output/cal_test.cpp is compiled into the main library
(source/source_io/CMakeLists.txt), not into a test target, and wrapped
parameter.h. The shipped binary therefore contained one translation unit that saw
Parameter::input / Parameter::sys as public while every other one saw them as
private.

The hack was also unnecessary: the file only reads PARAM.inp.nbands and
PARAM.globalv.nlocal, both public.

2. Sixteen regions that were never closed

These opened #define private public (usually protected too) and ran to end of
file — up to 1016 lines in charge_mixing_test.cpp. elecstate_pw_test.cpp was a
partial case: it closed protected 318 lines later at the end of the file and
never closed private at all.

Each #undef now sits immediately after the include block it is meant to cover.
No include is moved: in every one of these files the standard headers already came
after the project headers, so they simply fall outside the region once it is
closed. That removes the incidental treatment of <omp.h>, <string>,
<fstream>, <mpi.h>, <source_base/macros.h>,
<source_base/module_external/scalapack_connector.h> and "mpi.h" — eight files
were compiling a standard or system header with access control disabled.

Closing a region early is safe: the class definitions the tests reach into have
already been compiled by the time the #undef is seen.

3. Dead __MPI toggles, and two tests that asserted nothing

source/source_base/test/CMakeLists.txt:1 calls
abacus_disable_feature_definitions(__MPI), so __MPI is never defined for any
target in that directory and every #ifdef __MPI there is always false.

#7888 already fixed the worst of this — it removed the guards from
opt_cg_test.cpp / opt_tn_test.cpp and gave those targets a shared
mpi_test_main.cpp that actually initialises MPI. This PR clears what it left
behind:

  • math_chebyshev_test.cpp — guards still wrap checkconverge and
    checkconverge_float, so those two bodies are compiled out in every
    configuration: two tests reporting PASS while asserting nothing.
  • global_function_test.cpp — three #ifdef __MPI / #undef __MPI / #endif
    blocks whose test bodies sit outside the #ifdef. Pure no-ops.
  • opt_cg_test.cpp, opt_test_tools.cpp — a file-level #undef __MPI that only
    duplicates what CMake already does.

The #undef __MPI / #define __MPI pairs were no-ops regardless: the preprocessor
cannot retroactively change headers already processed above, and the library these
tests link is a separate translation unit compiled with __MPI either way.

The two chebyshev bodies this exposes cannot run in MODULE_BASE_math_chebyshev
they reach GlobalFunc::ddot_real -> Parallel_Reduce::reduce_pool, which needs a
valid POOL_WORLD, and that target has no MPI setup. They are also
byte-identical duplicates of the tests of the same names in
test_parallel/math_cheby_mpi_test.cpp (MODULE_BASE_math_chebyshev_mpi), which
sets POOL_WORLD up in its fixture and runs them properly. So they are deleted
rather than given a second MPI harness. No coverage is lost.

4. Dead code removed

  • source_io/test/for_testing_input_conv.h — included by nothing. It was the only
    header applying #define private public, and applied it to 22 module headers at
    once; it also defined non-inline globals at file scope, which would be a
    duplicate symbol the moment a second TU included it.
  • source_hsolver/test/test_diago_assist.cpp — in no CMakeLists.txt, includes a
    header that does not exist (diago_iter_assis.h; the real name is
    diago_iter_assist.h), and has a statement in a class body, so it cannot
    compile. Dead since [Refactor] Rename module_hsolver to source_hsolver #6305.
  • Six no-op regions: unitcell_test_setupcell.cpp and unitcell_test_pw.cpp had a
    bare #define private public immediately followed by #undef private;
    write_orb_info_test.cpp, rho_io_test.cpp, deltaspin_pw_test.cpp and the
    second region of bfgs_test.cpp wrapped only parameter.h and never touch a
    private member of Parameter.

Result

before after
#define private public in production code 1 0
in a header 1 0
regions never closed 17 0
standard/system headers compiled inside a region 8 0
tests compiled out unnoticed 2 0
files with the pattern 81 73
occurrences 111 102

Scope

This deliberately stays narrow. It does not try to remove the pattern from the
files that wrap the header of the class under test (unitcell.h, klist.h,
charge.h, sltk_grid.h, ...) to reach its private members. Those regions are now
all balanced, tight, and free of standard headers, but the hack is still there.

In particular, 41 translation units still compile parameter.h with access control
disabled, because their tests need to write PARAM.input / PARAM.sys. An earlier
revision of this PR routed those through the TestParameters friend class that
parameter.h already declares; that was not wanted, so it has been dropped. Per
AGENTS.md rule 10 the sanctioned remedies are to promote member visibility
explicitly or add a public test-only accessor — worth settling separately, since it
touches ~1600 call sites and should be a deliberate design decision rather than a
side effect of this cleanup.

read_wf2rho_pw_test.cpp likewise keeps its file-level #undef __LCAO: it is
load-bearing (it selects the non-LCAO path in the headers below it) and belongs in
that test's CMakeLists as abacus_disable_feature_definitions(__LCAO), but
changing it changes what the test compiles.

Review notes

  • Six commits, split by concern, each reviewable on its own.
  • No new files and no new interfaces: the diff is deletions plus 28 inserted
    #undef lines.
  • agent_governance_check.py --base upstream/develop: no blockers.
  • No INPUT parameter, interface, or runtime behaviour change outside test code, so
    no docs update is required.

Testing

Verified with gcc / Ninja, BUILD_TESTING=ON,
-DENABLE_LCAO=ON -DENABLE_MPI=ON -DENABLE_OPENMP=ON. Build clean.

Unit tests compared against an upstream/develop build from the same commit base
(fb9ce1da4), same configuration, ctest run serially with OMP_NUM_THREADS=1:

upstream/develop this branch
tests run 335 335
failures 29 29

No regressions and no differences — the failing set is identical on both trees.
Those 29 are pre-existing in this environment: mostly mpirun-based *_para /
*_parallel wrappers, plus tests in files this PR never touches
(blas_connector, math_sphbes, cubic_spline, PSI_init, dav, bpcg).

Integration tests (tests/) were not run locally; they need the full toolchain
build, and CI covers them.

@mohanchen mohanchen added Refactor Refactor ABACUS codes The Absolute Zero Reduce the "entropy" of the code to 0 labels Sep 4, 2026
@Critsium-xy
Critsium-xy force-pushed the fix/test-access-control-hack branch from 032bfe6 to 8de0ae1 Compare September 4, 2026 07:09

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this solution.

@Critsium-xy
Critsium-xy force-pushed the fix/test-access-control-hack branch from 8de0ae1 to 0f5f8a5 Compare September 7, 2026 05:09
zxy.monado and others added 6 commits September 7, 2026 13:59
`cal_test.cpp` is compiled into the main library (source/source_io/CMakeLists.txt),
not into a test target, and wrapped `parameter.h` in `#define private public`.
That gave this translation unit a definition of `Parameter` in which `input` and
`sys` are public, while every other translation unit in the library sees them as
private -- one class with two different definitions in one program, i.e. an ODR
violation.

The hack was also unnecessary: the file only reads `PARAM.inp.nbands` and
`PARAM.globalv.nlocal`, both public const references on `Parameter`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing includes this header -- it is referenced by no source file and by no
CMakeLists.txt. It was also the only header in the tree that applied
`#define private public`, and it applied it to 22 module headers at once
(`unitcell.h`, `elecstate_lcao.h`, `hsolver_lcao.h`, `force_stress_lcao.h`, ...),
so any translation unit that had included it would have compiled a large part of
the codebase with access control disabled.

On top of that it defined non-inline globals (`berryphase::berry_phase_flag`,
`elecstate::Gatefield::zgate`, ...) at file scope, which would be a duplicate
symbol as soon as a second translation unit included it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six regions did nothing at all and are removed:

- `unitcell_test_setupcell.cpp`, `unitcell_test_pw.cpp` had a bare
  `#define private public` immediately followed by `#undef private`, with no
  include in between -- left over from an earlier refactor.
- `write_orb_info_test.cpp`, `rho_io_test.cpp`, `deltaspin_pw_test.cpp` and the
  second region of `bfgs_test.cpp` wrapped only `parameter.h`, and none of them
  touches a private member of `Parameter`.

`test_diago_assist.cpp` is deleted. It is listed in no CMakeLists.txt, includes a
header that does not exist (`diago_iter_assis.h`; the real name is
`diago_iter_assist.h`), and has a statement in a class body
(`DIAGOTEST::hamilt.create(4, 4);`), so it cannot compile. It has been dead since
it was added in deepmodeling#6305.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`source/source_base/test/CMakeLists.txt:1` calls
`abacus_disable_feature_definitions(__MPI)`, so `__MPI` is never defined for any
target in that directory and every `#ifdef __MPI` in it is always false.

`opt_tn_test.cpp` and gave those two targets a real `mpi_test_main.cpp`. What
remains of the pattern is:

- `global_function_test.cpp` -- three `#ifdef __MPI / #undef __MPI / #endif`
  blocks. Their test bodies sit *outside* the `#ifdef`, so they always compiled;
  the blocks are pure no-ops.
- `math_chebyshev_test.cpp` -- guards wrapping `checkconverge` and
  `checkconverge_float`, so those two bodies were compiled out in *every*
  configuration: two tests reporting PASS while asserting nothing.
- `opt_cg_test.cpp`, `opt_test_tools.cpp` -- a file-level `#undef __MPI` that only
  duplicates what CMake already does.

The `#undef __MPI` / `#define __MPI` pairs were no-ops in any case: the
preprocessor cannot retroactively change headers already processed above, and the
library these tests link against is a separate translation unit, compiled with
`__MPI` regardless of what the test does.

The two chebyshev bodies this exposes are handled in the next commit.
The previous commit stops `checkconverge` and `checkconverge_float` from being
compiled out of `MODULE_BASE_math_chebyshev`. They cannot actually run in that
target: they reach `GlobalFunc::ddot_real` -> `Parallel_Reduce::reduce_pool`, which
needs a valid `POOL_WORLD`, and this target has no MPI setup -- unlike
`MODULE_BASE_opt_cg` / `MODULE_BASE_opt_tn`, which deepmodeling#7888 gave `mpi_test_main.cpp`.

They are also redundant: both are byte-identical to the tests of the same names in
`test_parallel/math_cheby_mpi_test.cpp` (`MODULE_BASE_math_chebyshev_mpi`), which
sets `POOL_WORLD` up in its fixture and runs them properly.

So delete the copies rather than stand up a second MPI harness for them. No
coverage is lost.
Sixteen test files opened `#define private public` (usually `#define protected
public` too) and never closed it, so from the include block onwards the whole
translation unit compiled with `private` and `protected` meaning `public` -- up to
1016 lines in `charge_mixing_test.cpp`. `elecstate_pw_test.cpp` was a partial case:
it closed `protected` 318 lines later at the end of the file, and never closed
`private` at all.

Each `#undef` now sits immediately after the include block it is meant to cover.
No include is moved: in every one of these files the standard headers already came
after the project headers, so they simply fall outside the region once it is
closed. That removes the incidental treatment of `<omp.h>` (charge_mixing_test),
`<string>` (elecstate_energy_test), `<fstream>` (verlet_test), `<mpi.h>` and
`<source_base/module_external/scalapack_connector.h>` (propagator_test1/2/3),
`<source_base/macros.h>` (test_hsolver) and `"mpi.h"` (read_wf2rho_pw_test).

Closing a region early is safe: the class definitions the tests reach into have
already been compiled by the time the `#undef` is seen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Critsium-xy
Critsium-xy force-pushed the fix/test-access-control-hack branch from 0f5f8a5 to 07d8a2e Compare September 7, 2026 06:02
@Critsium-xy Critsium-xy closed this Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Refactor Refactor ABACUS codes The Absolute Zero Reduce the "entropy" of the code to 0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants