Bug on Creating instances?

BlitzMax Forums/BlitzMax Programming/Bug on Creating instances?

BladeRunner(Posted 2013) [#1]
Ok, given the following Code:
SuperStrict
Local test:TBla
Try
	test:TBla = TBla.Create() 
	Print test.blub	'oh-oh, changing a null object
	Print "I never get executed"
	Catch TNull:TBlitzException
		Print "catch: " + Tnull.tostring() 'what happened?
EndTry
Print "Thanks to catch you can read me!"

test.blub = 1337
Print test.blub

Type TBla
	Field blub:Int
	
	Function Create:TBla() 
		Return Null
	End Function
End Type

Running it in debug mode will work as excepted. The first call of Test's int field will be catched. The second one will produce an exception.
Running it in release instead will make it buggy:
All Lines are executed except the catch block. So, there is an object Test in existance although it shouldn't be. You can even alter it's fields. Weird.
Any ideas on how to explain this?


Floyd(Posted 2013) [#2]
So, there is an object Test in existance

I don't think there is. This must be matter of unitialized memory.

The variable test, of type TBla, exists. But it doesn't point to any object created with New. You could probably work out the internal details of the Type system by wading through the module source code, but I've never done it .

The test variable must at least have some way of knowing whether it is valid, so it can be tested for equality with Null. That part is initialized as you would expect. It must also have a pointer to memory allocated to an object. That's the part which is uninitialized, containing random junk. I'm just guessing at the details, but I think the general idea is right.


Yasha(Posted 2013) [#3]
I was originally thinking the same as Floyd, but surely the point of having null reference exceptions is that they can be generated reliably? Some people (for reasons known only to them) actually code assuming such. (It's called EAFP - Easier To Ask Forgiveness Than Permission - and is common in interpreted and dynamic languages where there are type and null checks around every operation anyway; not so much in ones where the checks are optimised out.)

I think the code is behaving more or less as designed (for the reasons explained in post #2; to the extent that modifying null has a designed behaviour), but you could call it a "specification bug" if the exception type exists but doesn't fire in this case. Or does it? Is such an exception type even made available in release mode or is it only a debugging tool?

(Stuff I vaguely remember: Null in BlitzMax is a normal object with a very high reference count, to save the RC system the trouble of checking for null on every single assignment; this means it has to be safe to modify, at least for the runtime system; in turn, it can't be memory-protected to generate hardware exceptions "for free" the way true-null does in C++, so I guess there's no efficient way to make this work in release mode; if a value could be null, you should test it before use - ask permission, not forgiveness too late.)


BladeRunner(Posted 2013) [#4]
If I add a second field, a string, to the type and try to change it's values it will crash with a MAV, so I think this (code runs) is an unwanted case, what I would call a bug.
We did some deeper steps in the assembled code in the german forums and it seems that indeed there is one pointer for nulkl references and because it isn't checked in releasemode can be overwritten. Which is fatal if there is more than one integer (4 Bytes) changed.


Derron(Posted 2013) [#5]
(I have written the German posts).

I assume all findings there are correct ... but I did not think of using "try...catch" in my code. I just do not trust "foreign variables" and therefor check all of them versus NULL before accessing them.
The only exception is within methods and within "local obj:myobject eachin list" as that should only return objects of the wanted type.

Surely this adds some checks more than needed but this is no hpc-language.
People could also complain about "nullint:int = null" being "0" (which makes it harder to have null-params for functions to handle it with on default getters)

But nethertheless - it is a bug or language-design-mis-decision as being inconsistent a bit.


PS: I am not the one with much knowledge on this topic so @Yasha: does a real "null" add much overhead. I think that a null would have to get checked additionally while a "intNull=0" is just a ...int.

bye
Ron


Yasha(Posted 2013) [#6]
@Yasha: does a real "null" add much overhead.


By "real null" I meant the one used in C or C++, the one you get by casting 0 to a pointer type.

It's usually a pointer to a page of memory whose protection flags have been set to completely disallow reads or writes, so attempting to access the page will cause the hardware to raise a segfault signal (note that C doesn't actually define what happens here... C++ might, I don't know offhand; at any rate I'm talking about the common x86 case, where this happens). The language runtime can set up a signal handler to catch segfaults and turn them into language-level null reference exceptions if that's what happened; this way to handle exceptions is literally zero-overhead because the check doesn't happen until after null actually gets dereferenced; the exception originates in hardware (so no test gets put into the assembly code).

The downside is that you can only mprotect whole pages of memory at a time (4K on most systems). I guess Mark didn't want to waste a whole page of memory just to mprotect the "fields" of null or something? I don't know how it's actually allocated (as above, null actually gets its refcount adjusted like other objects so as to prevent needing extra tests in the assembly, so Blitz null would need a separate area overlapping two pages, not just to be placed at 0).

Weird combinations of addresses will also potentially defeat it if you get so wildly off track that the address accidentally becomes valid again, although you could design the language around this to prevent such things from being possible to express.


grable(Posted 2013) [#7]
Its more like a design flaw then a bug perhaps, as it would be too slow to do like debugmode does it.
My guess it was hard coded so as to not have to dereference a pointer on every null check early on, and then later exceptions came and it wasnt changed.

Of course i had to see if i could force the behavior anyway ;)
Sadly it would require tweaking the compiler as well as the runtime so its not perfect.

As Yasha said, this guards an entire page so it might guard stuff its not supposed to.. dont us it for production!
Oh, and win32 only.