@@ -152,6 +152,141 @@ TEST_F(XCTest_GRADCORR, set_xc_type)
152152 }
153153}
154154
155+ // Regression test for an out-of-bounds read in gradcorr.
156+ // `set_xc_type("HF")` sets func_type = 4 (hybrid) but leaves func_id EMPTY, because pure
157+ // Hartree-Fock has no (semi-)local functional. gradcorr used to index func_id[0]/func_id[1]
158+ // unconditionally (see also the commented-out loop in XC_Functional::gcx_spin), reading back
159+ // whatever the previously selected functional had left in the cleared-but-not-deallocated
160+ // buffer. In the two-level EXX loop that predecessor is PBE (set_xc_first_loop), so a pure-HF
161+ // run silently picked up a PBE gradient correction (issue deepmodeling/abacus-develop#5404)
162+ //
163+ // Setting PBE *before* HF is therefore an essential part of this test: it is what leaves the
164+ // stale ids in the buffer, and without it the regression does not reproduce.
165+ class XCTest_GRADCORR_HF : public XCTest
166+ {
167+ protected:
168+
169+ double et1 = 0 , vt1 = 0 ;
170+ ModuleBase::matrix v1;
171+ std::vector<double > stress1;
172+
173+ double et2 = 0 , vt2 = 0 ;
174+ ModuleBase::matrix v2;
175+ std::vector<double > stress2;
176+
177+ double et4 = 0 , vt4 = 0 ;
178+ ModuleBase::matrix v4;
179+ std::vector<double > stress4;
180+
181+ void SetUp ()
182+ {
183+ const int nspin1 = 1 ;
184+ const int nspin2 = 2 ;
185+ const int nspin4 = 4 ;
186+ const bool domag = false ;
187+ const bool domag_z = false ;
188+ const bool domag_true = true ;
189+
190+ ModulePW::PW_Basis rhopw;
191+ UnitCell ucell;
192+ Charge chr;
193+
194+ rhopw.nrxx = 5 ;
195+ rhopw.npw = 5 ;
196+ rhopw.nmaxgr = 5 ;
197+ rhopw.gcar = new ModuleBase::Vector3<double > [5 ];
198+
199+ ucell.tpiba = 1 ;
200+ ucell.magnet .lsign_ = true ;
201+ unitcell::cal_ux (ucell, 4 );
202+
203+ chr.rho = new double *[4 ];
204+ chr.rho [0 ] = new double [5 ];
205+ chr.rho [1 ] = new double [5 ];
206+ chr.rho [2 ] = new double [5 ];
207+ chr.rho [3 ] = new double [5 ];
208+ chr.rhog = new std::complex <double >*[2 ];
209+ chr.rhog [0 ] = new std::complex <double >[5 ];
210+ chr.rhog [1 ] = new std::complex <double >[5 ];
211+
212+ chr.rho_core = new double [5 ];
213+ chr.rhog_core = new std::complex <double >[5 ];
214+
215+ for (int i=0 ;i<5 ;i++)
216+ {
217+ chr.rho [0 ][i] = double (i);
218+ chr.rho [1 ][i] = 0.1 *double (i);
219+ chr.rho [2 ][i] = chr.rho [0 ][i];
220+ chr.rho [3 ][i] = chr.rho [1 ][i];
221+ chr.rhog [0 ][i] = chr.rho [0 ][i];
222+ chr.rhog [1 ][i] = chr.rho [1 ][i];
223+ chr.rho_core [i] = 0 ;
224+ chr.rhog_core [i] = 0 ;
225+ rhopw.gcar [i]= 1 ;
226+ }
227+
228+ v1.create (1 ,5 );
229+ v1.zero_out ();
230+ v2.create (2 ,5 );
231+ v2.zero_out ();
232+ v4.create (4 ,5 );
233+ v4.zero_out ();
234+
235+ // leave PBE ids in func_id's buffer, then switch to pure HF
236+ XC_Functional::set_xc_type (" PBE" );
237+ XC_Functional::set_xc_type (" HF" );
238+
239+ const double hybrid_alpha = 1.0 ;
240+ const double hse_omega = 0.0 ;
241+ XC_Functional::gradcorr (et1,vt1,v1,&chr,&rhopw,&ucell,stress1,false ,nspin1,domag,domag_z, hybrid_alpha, hse_omega);
242+ XC_Functional::gradcorr (et1,vt1,v1,&chr,&rhopw,&ucell,stress1,true , nspin1,domag,domag_z, hybrid_alpha, hse_omega);
243+
244+ XC_Functional::gradcorr (et2,vt2,v2,&chr,&rhopw,&ucell,stress2,false ,nspin2,domag,domag_z, hybrid_alpha, hse_omega);
245+ XC_Functional::gradcorr (et2,vt2,v2,&chr,&rhopw,&ucell,stress2,true , nspin2,domag,domag_z, hybrid_alpha, hse_omega);
246+
247+ XC_Functional::gradcorr (et4,vt4,v4,&chr,&rhopw,&ucell,stress4,false ,nspin4,domag_true,domag_z, hybrid_alpha, hse_omega);
248+ XC_Functional::gradcorr (et4,vt4,v4,&chr,&rhopw,&ucell,stress4,true , nspin4,domag_true,domag_z, hybrid_alpha, hse_omega);
249+ }
250+ };
251+
252+ TEST_F (XCTest_GRADCORR_HF, no_local_functional)
253+ {
254+ // the state the fixture ran under: hybrid, but with no (semi-)local functional
255+ EXPECT_EQ (XC_Functional::get_func_type (), 4 );
256+ EXPECT_TRUE (XC_Functional::get_func_id ().empty ());
257+
258+ // pure HF must contribute exactly nothing through the gradient correction
259+ EXPECT_DOUBLE_EQ (et1, 0.0 );
260+ EXPECT_DOUBLE_EQ (vt1, 0.0 );
261+ EXPECT_DOUBLE_EQ (et2, 0.0 );
262+ EXPECT_DOUBLE_EQ (vt2, 0.0 );
263+ EXPECT_DOUBLE_EQ (et4, 0.0 );
264+ EXPECT_DOUBLE_EQ (vt4, 0.0 );
265+
266+ for (int i=0 ;i<5 ;i++)
267+ {
268+ EXPECT_DOUBLE_EQ (v1 (0 ,i), 0.0 );
269+ EXPECT_DOUBLE_EQ (v2 (0 ,i), 0.0 );
270+ EXPECT_DOUBLE_EQ (v2 (1 ,i), 0.0 );
271+ for (int is=0 ;is<4 ;is++)
272+ {
273+ EXPECT_DOUBLE_EQ (v4 (is,i), 0.0 );
274+ }
275+ }
276+
277+ // `Stress_Func::stress_gga` guards only on func_type, so it reaches this path for HF and
278+ // then reads stress_gga[0..8]: the tensor must come back sized and zeroed, not empty.
279+ ASSERT_EQ (stress1.size (), 9 );
280+ ASSERT_EQ (stress2.size (), 9 );
281+ ASSERT_EQ (stress4.size (), 9 );
282+ for (int i=0 ;i<9 ;i++)
283+ {
284+ EXPECT_DOUBLE_EQ (stress1[i], 0.0 );
285+ EXPECT_DOUBLE_EQ (stress2[i], 0.0 );
286+ EXPECT_DOUBLE_EQ (stress4[i], 0.0 );
287+ }
288+ }
289+
155290class XCTest_GRADWFC : public XCTest
156291{
157292 protected:
0 commit comments