0

So I decided to get rid of singletons in my project and introduce dependency injection. I did all the necessary changes, and I got a little problem: no matter what I did, my NetworkService was called anyway, regardless of the fact it was initialised to nullptr. I started to investigate, and I got an impossible scenario. I'm feeling powerless, and I give up. I don't know how THIS code gets executed without issues:

    auto impossible_response = ((NetworkService*)nullptr)->post(
            format_url("/api/data/fetch/"),
            payload.dump(),
            headers);
    log.crit("How did this succeeded? Please help me, StackOverflow");

Log message

I'm compiling my code on ArcoLinux with G++ (C++20) via Gradle. I've already tryied to rebuild it from scratch without any cache from previous builds.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 6
    Why on earth do you find `((NetworkService*)nullptr)->` reasonable? It's a _perfect_ case of Undefined behavior. You dereference a null pointer. Just stop it right there. – Ted Lyngmo May 26 '22 at 16:47
  • 6
    Just because an object is `nullptr`, it doesn't mean you can't call methods on it. It seems that this particular method doesn't need the instance at all. – freakish May 26 '22 at 16:48
  • I dunno dude... I just tryied to find an impossible scenario to run, to crash the program on purpose... And I failed...... – Iaroslav Sorokin May 26 '22 at 16:50
  • 4
    Undefined behavior does not mean the program will crash. One of the worst behaviors of UB is when the code appears to work when it's broken. – drescherjm May 26 '22 at 16:50
  • "My code works and I don't know how" is the dark side of the development coin – Rogue May 26 '22 at 16:50
  • Even If I try to dereference it on purpose, it DOES succeed – Iaroslav Sorokin May 26 '22 at 16:50
  • 2
    @IaroslavSorokin Undefined behavior may look successful. Don't leave it like that. The observable success may also contain less nice side effects. – Ted Lyngmo May 26 '22 at 16:51
  • 3
    @IaroslavSorokin Undefined behavior means anything can happen including but not limited to the program giving your expected output. But never rely on the output of a program that has UB. The program may just crash. – Jason May 26 '22 at 16:51
  • @IaroslavSorokin *to crash the program on purpose* -- Good luck with that, since there is no official way to crash any C++ program. – PaulMcKenzie May 26 '22 at 16:51
  • 1
    I get it, so this is clearly an undefined behaviour. I thought any call to nullptr (or an attempt to dereference it) would crash the program, but it seems I was wrong – Iaroslav Sorokin May 26 '22 at 16:54
  • @IaroslavSorokin -- The other thing to consider is that the compiler knows it's nonsense code you are invoking, and thus may remove it entirely from the executable. It's the same if you wrote this: `if (this == nullptr)` -- when a compiler sees that, it says to itself "that can never happen", and thus remove it from the final executable. A compiler is allowed to do this. – PaulMcKenzie May 26 '22 at 16:54
  • @IaroslavSorokin the only way to crash program on purpose is to (1) deeply understand hardware you use, (2) deeply understand OS you use, (3) deeply understand compiler you use and (4) deeply understand the code you write. It is extremely unlikely to write a program that will always crash, and in the same way. – freakish May 26 '22 at 16:55
  • Usually treats a non-static member function as a free function of the same signature with an additinal parameter that is a pointer to the containing type passed as first parameter. E.g. in `struct Foo { int bar() { return 1;} };` `bar` gets turned into something like `int bar(Foo* this) { return 1; }` this explains why the code can work: calling `bar(nullptr)` is a non-issue, since the null pointer is not actually dereferenced in the binary. There's no guarantee for this tough... – fabian May 26 '22 at 17:00
  • Try calling this to "crash" the program: `std::abort();`, which will cause an *abnormal program termination*. – Eljay May 26 '22 at 17:00
  • If you want to crash your program on purpose try reading from a null pointer, e.g. `std::cout << *static_cast(nullptr) << '\n';`. – fabian May 26 '22 at 17:02
  • @fabian • that works for some platforms, but not others. My embedded controller doesn't have an operating system, and doesn't have a PMMU, and memory address 0x00000000 is legitimately accessible in the manner which you've done. (Also doesn't have streaming I/O, and doesn't have malloc/free or new/delete, and doesn't have exceptions, nor RTTI. Rather a constrained platform.) – Eljay May 26 '22 at 17:07
  • Right when I was almost finished writing my answer, the question got closed right under my butt. Super frustrating. Don't want what I wrote just contribute to the Universe's heat death, so I pasted it on pastebin. Enjoy: https://pastebin.com/cTqGbb42 – datenwolf May 26 '22 at 17:20

2 Answers2

5

Even If I try to dereference it on purpose, it DOES succeed.

No, the program has undefined behavior meaning it is still in error even if it doesn't say so explicitly and "seems to be working". This is due to the use of -> for dereferencing in your program.

Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior. The program may just crash.

So the output that you're seeing(maybe seeing) is a result of undefined behavior. And as i said don't rely on the output of a program that has UB. The program may just crash.

So the first step to make the program correct would be to remove UB. Then and only then you can start reasoning about the output of the program.


1For a more technically accurate definition of undefined behavior see this, where it is mentioned that: there are no restrictions on the behavior of the program.

Jason
  • 36,170
  • 5
  • 26
  • 60
0

Little magic. If function doesn't access any class members, calling it by any invalid pointer can go unnoticed:

struct A {  void func() {cout<< "hello world"<< endl;} };

int main()
{
  ((A*)(nullptr))->func();
}

Not sure that standards say, but in this case function behaves mostly like a global function

Update
In order to anticipate some discussions, the experiment is the way of truth. I made the modes in different modes, debug/release/x64/x32, OS Windows/Solaris, compilers VisualStudio 2022/2019, Embarcadero, g++, all shown exactly the same behavior. I have tried deliberately to make the code to fail. All result are the same. No success code forced to be returned from main function.
Here is VisualStudio under Windows: enter image description here Here is g++ under Solaris, failed only the version which explicitly returned failure return code: enter image description here

armagedescu
  • 1,758
  • 2
  • 20
  • 31
  • 3
    Still **undefined behavior**. Causes a **runtime error** on my machine, `runtime error: member call on null pointer of type 'A'`. – Eljay May 26 '22 at 17:03
  • At first I assumed there were some compiler optimizations (correct me if I'm wrong), which transform methods that doesn't use any data from the instance into static functions and call them instead. But I didn't find any info on the matter, so I couldn't confirm it – Iaroslav Sorokin May 26 '22 at 17:03
  • @Eljay try release mode – armagedescu May 26 '22 at 17:12
  • Release mode won't make the code any less **undefined behavior**. – Eljay May 26 '22 at 17:18
  • @Eljay Release mode dosn't contain debug information and checks for calls by null. It just executes as is, low level and direct. And there is nothing to fail in fact. – armagedescu May 26 '22 at 17:21
  • Also fails in release mode (`-O3`). But since it is **undefined behavior**, any behavior is allowed. Including runtime error, or appearing to work. – Eljay May 26 '22 at 17:27
  • Raymond Chen has a nice article about **undefined behavior**, [UB can result in time travel](https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633). At the tail end of the article he links to Chris Lattner's *What Every C Programmer Should Know About Undefined Behavior* articles. That's why UB makes the *entire* program ill-formed (IF/NDR). Intentionally adding UB to a program is the path to madness. – Eljay May 26 '22 at 18:03
  • @Eljay The result of scientific experiment. Tried with different compilers (g++, vc, embarcadero), modes(x64/x32), processors(intel/amd/sparc) and OS (windows/solaris). All executed perfectly, returning 0 where I didn't explicitly forced to return failure, none had undefined behavior, all standard compile, no tricks, no hidden catch. – armagedescu Jun 01 '22 at 13:37
  • *... none had undefined behavior...* All had **undefined behavior**. If you are lucky, undefined behavior will result in a crash or a runtime error. If you are unlucky, it may appear to work as expected. If you want to dance with UB, that's your call, and I hope you'll get gainfully employed at my competitor's company. – Eljay Jun 01 '22 at 13:53
  • @Eljay make the difference between scientific experiment and production code. Experiments are made to gain the knowledge. If you believe is about luck, then anyone is free make the same experiment and confirm or reject the results. – armagedescu Jun 01 '22 at 14:06
  • As I already mentioned, it causes a runtime error due to undefined behavior on my machine. – Eljay Jun 01 '22 at 14:07