Problem 2347

Summary: Integer overflow problem with empty string
Product: Geant4 Reporter: Simon Spannagel <simon.spannagel>
Component: interface/basicAssignee: John.Allison
Status: RESOLVED FIXED    
Severity: minor CC: wdconinc
Priority: P4    
Version: 10.7   
Hardware: All   
OS: All   

Description Simon Spannagel 2021-03-15 08:36:46 CET
Dear developers,

with the recent changes to source/interfaces/basic/src/G4UIQt.cpp there has been a bug introduced which results in a segfault in some circumstances.

In
G4int G4UIQt::ReceiveG4cout(...)
you have added the following loop (here: https://github.com/Geant4/geant4/blob/master/source/interfaces/basic/src/G4UIQt.cc#L2045-L2055)

for (size_t i = 0; i < aString.length() - 1; ++i) {
    // access aString[i]
}

without a prior check for the actual length of aString. At the beginning of the function, you check for 

if (!aString) return 0;

but for me this does not suffice. For the case where aString exists but is empty, the check passes with true, continuing to the loop where we get the unsigned integer overflow through

size_t i < aString.length() - 1

where the length in my case is 0.

I suggest adding an additional check to the beginning of the function:

if (!aString || aString.empty()) return 0;

to also catch this.

Best regards,
Simon
Comment 1 John.Allison 2021-03-17 15:47:40 CET
Hi Simon. Good spot. Thanks.

Question: Is an empty string deliberate? E.g., "G4cout << G4endl;". Should we print a newline rather than iginore it?

John
Comment 2 Simon Spannagel 2021-03-17 16:01:29 CET
Hi John,

at least in my use case the empty string is deliberate because I control the Geant4 printout by suppressing its stream in some situations via

G4cout.setstate(std::ios::failbit)

For some of the UI commands this seems to result in an empty string being passed to the above function.

So for me, both solutions would work fine, I'm not sure how others use this or expect this to work.

All the best,
Simon
Comment 3 John.Allison 2021-03-17 17:21:07 CET
Hi Simon

I have been trying to recreate your segfault...

Under normal circumstances I find that on my MacBook the output string is never empty - it contains, at least, '\n'.

I'm not saying we should not put in place your proposed extra level of protection - good idea - but...

  G4cout.setstate(std::ios::failbit)

seems to me to be asking for trouble. If I do that it disables the output on the master thread for all subsequent output, yet output from the worker threads continues. And I don't get a segfault.

Are you using setstate to disable output? Maybe there's another way. I can't think of one at the moment but if this is your requirement we could think about it.

Cheers
John
Comment 4 Simon Spannagel 2021-03-17 17:28:07 CET
Hi John,

yes, I use this to disable the output selectively. I enable it in some circumstances. I have a local logger class which defines different severity levels, and going below a certain threshold, I enable G4 output as "debug information" basically. Not beautiful but works.

This is indeed a per-thread thing since you keep different stream instances around. I find this rather practical, and since I roll my own threads anyway, I can control verbosity per-thread.

There is only one single point in my whole simulation where this problem appears, which is when I call

  G4UImanager* UI = G4UImanager::GetUIpointer();
  UI->ApplyCommand("/vis/scene/add/trajectories smooth rich");

I did not dig deeper into why this happens but just went to the root cause at the top of the segfault, so I fear I'm not much of a help with that...

Simon
Comment 5 John.Allison 2021-03-17 17:44:28 CET
Hi Simon, What OS are you using?
Comment 6 Simon Spannagel 2021-03-17 17:54:08 CET
I personally am using Ubuntu 20.10 / 64bit, but this has also been reported by a user (here: https://gitlab.cern.ch/allpix-squared/allpix-squared/-/issues/195) and I'm not sure what system they run on.
Comment 7 John.Allison 2021-03-18 12:29:17 CET
Hi Simon

I cannot reproduce your segfault. I have a feeling these things are compiler dependent.

The statement

if (!aString) return 0;

implies there is an implicit conversion of string to bool. But looking at C++ documentation for string at cplusplus.com I find no mention of it. Googling "implicit conversion of C++ string to bool" throws up some discussion at stackoverflow.com that seems to imply that it is equivalent to empty(). Or maybe not. To be safe I suggest simply changing this to

if (string.empty()) return 0;

Let us see if others have any further comments on this.

(I hasten to say I was not personally responsible for introducing the original statement.)

This all raises the question of how an empty string found its way into this function in the first place. It never seems to happen for me, even following what you did.

I certainly disapprove of using

G4cout.setstate(std::ios::failbit)

to control the output. I assume allpix-squared uses the same trick. If this is a user requirement, please bring it up in the Technical Forum and we can see if we can offer a sensible way of limiting output.

All the best
John
Comment 8 Simon Spannagel 2021-03-18 13:12:29 CET
Hi John,

I don't even manage to compile an example here, because:

void _str(const std::string& str) {
  std::cout << "to bool: " << static_cast<bool>(str) << std::endl;
  std::cout << "to bool: " << (!str) << std::endl;
}

results in:

string_bool.cpp: In function ‘void _str(const string&)’:
string_bool.cpp:6:52: error: invalid static_cast from type ‘const string’ {aka ‘const std::__cxx11::basic_string<char>’} to type ‘bool’
    6 |   std::cout << "to bool: " << static_cast<bool>(str) << std::endl;
      |                                                    ^
string_bool.cpp:7:32: error: no match for ‘operator!’ (operand type is ‘const string’ {aka ‘const std::__cxx11::basic_string<char>’})
    7 |   std::cout << "to bool: " << (!str) << std::endl;
      |                                ^~~~
string_bool.cpp:7:32: note: candidate: ‘operator!(bool)’ <built-in>
string_bool.cpp:7:32: note:   no known conversion for argument 1 from ‘const string’ {aka ‘const std::__cxx11::basic_string<char>’} to ‘bool’

this is standard

$ g++ --version
g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

so I think the .empty() is the proper way to check this.

Concerning the stream suppression I agree, its rather a hacky solution, I'll come to one of the technical forum meetings to discuss!

Thanks,
Simon
Comment 9 John.Allison 2021-03-18 15:06:43 CET
OK, Simon. Thanks.

One final observation: If in your function _str you replace string by G4String, it compiles, probably because G4String has

inline G4String::operator const char*() const { return c_str(); }

This means !aString will always be false, so the statement

if (!aString) return 0;

is useless. So this is definitely a bug and we will fix it.

Thanks for all your serious effort and debugging.

John
Comment 10 Gabriele Cosmo 2021-04-14 08:16:54 CEST
*** Problem 2358 has been marked as a duplicate of this problem. ***
Comment 11 John.Allison 2021-04-14 15:10:29 CEST
Fixed in interfaces-V10-07-07:

18 March 2021  John Allison  (interfaces-V10-07-07)
- G4UIQt::ReceiveG4cout/G4cerr:
  o Trap empty string with empty() instead of using unary operator!
    (The latter may work for std::string but G4String has a conversion
    operator to char*, so ! is always false.)
  o Fixes bug report #2347.