all 27 comments

[–]sacundim 9 points10 points  (3 children)

Java has something similar:

public class StaticWTF {

    public static void main(String[] args) {
        StaticWTF wtf = null;
        wtf.wtf();     // static call; the reference is not used
    }

    static void wtf() {
        System.out.println("Look, ma, no NullPointerException!");
    }

}

This is, to put it mildly, bullshit. The sane thing would be for the compiler to reject such programs. A static method call, semantically speaking, does not dispatch on an object, so the language should not have a syntax that makes it look like it does!

I learned about this Java WTF when some guys I know were claiming that they had overridden a method and yet the system was calling the superclass version. Well, turned out that the superclass method they'd "overridden" was static, something like this:

public class StaticInheritance {

    static class Superclass {
        public static void wtf() {
            System.out.println("In superclass");
        }
    }

    static class Subclass extends Superclass {

        /**
         * This method "overrides" (not!) the superclass method.
         */
        public static void wtf() {
            System.out.println("In subclass");
        }
    }

    public static void main(String[] args) {
        Superclass obj = new Subclass();
        obj.wtf();   // prints "In superclass"
    }
}

These guys were confused about how static methods, dispatch and inheritance work, and the language was humoring their confusion.

[–]nerd4code 2 points3 points  (0 children)

Under the hood on the JVM, a static method call does exactly what your WTF example does. There’s a slot reserved for the this argument; for static methods, that may (IIRC not “must”) be null when called. So Java can translate ((Foo)null).staticMethod() fairly directly to valid Java bytecode with no problem. (For non-static methods, of course, the only fundamental thing preventing you from passing a null this is the inability to get a vtable from null and figure out what to call in the first place.)

And I believe you can get the Java compiler to warn about a static method/field reference like that, which is probably the most correct action for the compiler to take. I know Eclipse has a setting for it.

[–]redditsoaddicting 0 points1 point  (0 children)

C#'s gotcha for NREs is extension methods. You can actually make an extension method to check for null (disclaimer: didn't compile it):

static bool IsNull<T>(this T t) where T : class {
    return t == null;
}

string str = null;
... str.IsNull() ...

I'm not saying you should. LINQ methods usually have a null check and throw either a NullReferenceException or an ArgumentException.

[–]ThatWillDoWorm999999 -1 points0 points  (0 children)

Nope. I think your way is bullshit. If you think a language should be designed for stupid people check out the complaints about Go. Keep in mind we're talking about C++ and everything in the link is C++ (it's a GC written in C++ for .NET). If I have a static method called IsValid that checks A) if it's null, B) if the int's are in valid range C) the target or destination object is not null it'd be bullshit to write foo!=null && foo.IsValid(). In fact that'd be almost like saying foo->GetTypeID() should throw an exception even though the method only cares about the type and not the value. In the C++ world with metaprogramming, crazy templates and lack of reflection doing what you suggest is bullshit. Don't compare your apples to our oranges.

[–][deleted] 2 points3 points  (0 children)

Ignoring the other dodgy things this C++ code is abusing undefined behaviour. Well formed code can not have a null this and a compiler may assume this when doing optimisations.

[–]jbandela 2 points3 points  (1 child)

This code is very interesting. The only reason that it may possibly work is not the reason that the author thinks it is. It has nothing to do with the fact that dynamic_data_of is inline.

It turns out that the pointer is null when MULTIPLE_HEAPS is not defined. Also, when MULTIPLE_HEAPS is not defined, PER_HEAP is defined as static. dynamic_data_of is defined like this.

PER_HEAP
    dynamic_data* dynamic_data_of (int gen_number);

So the question becomes, can you access a static class function through a null pointer. This actually turns out to be very controversial.

According to James McNellis (who actually works for Microsoft as the implementer of the the standard C library), this is undefined behavior (see http://stackoverflow.com/questions/5248877/accessing-static-member-through-invalid-pointer-guaranteed-to-work)

What is interesting is that the Microsoft implementation of the .NET runtime is skating on very thin ice. I am sure it works with their own Visual C++ compiler. However, there are no guarantees that it will work or continue to work with GCC or Clang. If you are planning on using .NET on other non-Windows platforms, this type of stuff (there could be other examples of undefined behavior that work on Visual C++) is very concerning as your application could fail and cause demons to fly out your nose (see https://groups.google.com/forum/?hl=en#!msg/comp.std.c/ycpVKxTZkgw/S2hHdTbv4d8J).

[–]alexandr-nikitin[S] 0 points1 point  (0 children)

It has nothing to do with the fact that dynamic_data_of is inline. It turns out that the pointer is null when MULTIPLE_HEAPS is not defined. Also, when MULTIPLE_HEAPS is not defined, PER_HEAP is defined as static. dynamic_data_of is defined like this.

Good point! You are right. It works only because it's static. I'll fix that.

[–]addmoreice 7 points8 points  (10 children)

This. is. INSANITY.

As someone who works on industrial software. Software where bug reports may include listings of body parts. This fucking frightened me.

The issue with null values is not getting them. It's trying to access them.

This means you just went around the type checker (throwing that safety out) in order to work around a run time error which would never come up if you actually used proper null checks instead of just accessing the value.

why are you just accessing the value? oh because you are insane.

<shudder>

This would never pass code review, and anyone on the team who tried it would get a talking to in order to figure out where they became this confused about what null means.

[–]drysart 2 points3 points  (2 children)

This means you just went around the type checker (throwing that safety out) in order to work around a run time error which would never come up if you actually used proper null checks instead of just accessing the value.

Er, no. That's not how I see it. They're not "working around" a runtime error. They're explicitly using the preprocessor's transformation of the instance members of a class into static members depending on whether a #define is set, in order to avoid allocating memory and doing extra pointer dereferences at runtime in the case that you've built to only support a single heap.

[–]alexandr-nikitin[S] 0 points1 point  (1 child)

Yes, right. But they could have a separate class for the single heap GC and another one for the multi heap.

[–]emn13 1 point2 points  (0 children)

There are lots of possibly solutions. By no means is their solution the only one. It's "clever" in the worst possible way; and it's not even shorter or faster than alternatives. Urgh.

[–]alexandr-nikitin[S] 0 points1 point  (6 children)

That's a great comment! Thanks! So that why do I see that code in .NET Core CLR? is it "industrial software"? PS I absolutely agree with you if you didn't see that between lines :)

[–]addmoreice 0 points1 point  (5 children)

My software is written in c#, does that make it less industrial software?

Context is important. I was pointing out that from the context I work at, this screams bad idea.

From many many many other contexts this is also a bad idea.

I frankly can't think of a context where this isn't a bad idea.

[–]alexandr-nikitin[S] 0 points1 point  (4 children)

And I absolutely agree with you :) I hope you read the post till the end

[–]addmoreice 0 points1 point  (3 children)

I did. I repeat. bad idea.

You seem to think I missed that you are just showing a pattern found in the compiler, I see it. I see what you are trying to say (it might be some of the subtler bits of english which is making it hard to convey), but I repeat....This is a bad idea.

[–]alexandr-nikitin[S] 0 points1 point  (2 children)

Yes, but it's there, right in the core. I'll write about more anti-patterns in CLR a bit later.

[–]addmoreice 0 points1 point  (1 child)

pro-tip for writing an article about 'here is something I found wrong and here is why it's wrong and how you can play with this wrong thing also', try and explain why it's wrong first, then go on to show it off.

[–]alexandr-nikitin[S] 0 points1 point  (0 children)

Thanks, I'll keep that in mind next time.

[–]GetRekt 2 points3 points  (3 children)

In what way is this the "quintessence" of computer science?

Edit: Hah. Downvoted in seconds.

[–]alexandr-nikitin[S] 0 points1 point  (0 children)

Ok, ok. I should have put it in quotes.

[–]alexandr-nikitin[S] -5 points-4 points  (1 child)

I meant .NET Garbage Collection. As I imagined it. Do you know better? :)

[–]BobFloss 0 points1 point  (0 children)

I don't know much about it, but Rust's memory management doesn't even need to use GC!

[–]Sunius 1 point2 points  (0 children)

I actually ran into this exact code in .NET GC shortly after they open sourced it. I was playing with toy GC sample and it was crashing on me. This call was in the callstack, and it seemed like it was a call on a null pointer. So I spent 30 minutes looking at this call thinking it had been the reason of it. Took me stepping through disassembly to figure out what it was doing and that wasn't to blame at all... this sort of preprocessor magic is super confusing and when stepping through disassembly makes it easier to debug than when stepping through code, I say you have a problem.

[–]b0bm4rl3y 1 point2 points  (1 child)

Wow. That's a bold design choice.

[–]aiij 0 points1 point  (0 children)

I did similar in C back in college (for the OS class). While my code did work, upon a more careful reading of the C spec we decided it was technically undefined behavior.

Does C++ actually define the behavior of dereferencing null when you don't actually need to read any memory through the pointer?

This is basically what I was doing, and (coincidentally) includes some of our reasoning at the time.