Block-level Dim Gotcha

| | Comments (8)

I spent quite some time the other night trying to debug a problem I ran across while updating the Windows Functionality Suite. I've been using REALbasic 2005 (formerly known as RB 6), and so that means I've also been taking advantage of many of its new features. One of these features I am making use of is the ability to do block level dims.

If you're not familiar with what a block level dim is, here's a quick lesson. In previous versions of REALbasic, you could only declare variables in the method scope. This means that they could not be inside of an if block, or a for loop. But now, you're able to declare them anywhere you want, like this:
[rbcode]
if foobar = 42 then
Dim wahoo as String = "Testing"
end if
[/rbcode]
The variable wahoo is only accessible from within the if block that contains it. This is a very nice new feature since it means you can encapsulate your local variables better -- it makes copy and pasting code much easier!

The gotcha that I ran into was the fact that the variable goes out of scope at the end of the if block. This makes logical sense in most instances, but there's one place where it's bound to catch others just like it caught me: MemoryBlocks.

I wrote some code like this:
[rbcode]
Dim mb as new MemoryBlock( 4 )
if foobar then
Dim theText as new MemoryBlock( 1024 )
if wahoo then
theText.WString( 0 ) = "Some wide string"
else
theText.CString( 0 ) = "Some C string"
end if
mb.Ptr( 0 ) = theText
end if
[/rbcode]
The code looks very reasonable. All it's doing is creating a pointer to a string with the appropriate data stuffed into it. So now mb is a pointer to a string that can be passed into a Win32 declare.

The problem comes into play with the fact that leaving the if block which declared theText causes theText to be destroyed. So now mb.Ptr( 0 ) points to a block of released data -- effectively garbage. The real danger here is in the fact that the garbage frequently contains most, if not all of the data you stuffed into that pointer. That makes this a very difficult bug to track down!

So how can this be fixed? Well, aside from the obvious answer of not dim'ing a MemoryBlock inside of a non-method scoped block, there is at least one answer I can think of. MemoryBlock.Ptr could treat all owned pieces of data as reference counted objects. So when the encompassing MemoryBlock is destroyed, it can look at its list of sub-items that it owns and decrement the lock counter on them. This would cause the above example to work since theText would have an extra reference held on it from mb. And when mb goes out of scope, theText would be taken with it.

8 Comments

I don't get why you would want to/expect that a variable within such a block would exist outside it's declared scope. That sounds like a Perl thing to me. I can see how it could be useful, make no doubt about it, but from the perspective of keeping things clean, when I declare a variable within a certain scope, I absolutely want that variable to disappear when that block goes away. If it doesn't, then there's little point in having multiple lexical levels -- you might as well declare them all at the top of your method.

But that's my opinion. Obviously you have a wee bit more experience in this type of thing than I do ;)

Because you're assigning one reference counted variable to another. To me, MemoryBlock.Ptr is no different than Dictionary.Value. If Dictionary.Value is handed a reference counted object, the reference should be incremented (which it is). So this is perfectly legal:

dim foo as new Dictionary
if bar = 42 then
dim item as new MemoryBlock( 50 )
foo.Value( bar ) = item
end if

MsgBox MemoryBlock( foo.Value( bar ) ).Long( 0 )

Yes, but what happens if you say:

mb.Ptr(4) = mb

Now, you have something you can't get rid of.

True, but here's a more realistic exmaple:

dim mb as new MemoryBlock( 4 )
mb.Ptr( 0 ) = someOtherMemoryBlock

dim foobar as MemoryBlock
foobar = mb.StringValue( 0, mb.Size )

Now mb can go out of scope, and drag someOtherMemoryBlock with it, but foobar still has a pointer to someOtherMemoryBlock even though that's been destroyed.

The reason why this feature has never been done, and why it won't ever be done, is because a MemoryBlock is an arbitrary chunk of bytes that you can manipulate in any way you want. There's always going to be cases where the user can do things to "break" stuff.

I'm not saying that the MemoryBlock should disappear just because you're outside of the block. I guess I wasn't very clear in the way I wrote my first comment. The memory space originally allocated for the variable shouldn't be 'cleared' when you exit the block, but you shouldn't be able to reference it by name outside of it. That is:

Dim c As New myClass
If some_condition Then
Dim Foo As myMember
c.member = Foo
End If

MsgBox Foo.AsString // Should fail -- what's 'foo'??
MsgBox c.member.AsString // Should work

Oh, yes! I certainly agree with you there. In that regard, things are already working like that (and they should continue to do so). I wasn't meaning to imply that the variable scoping was incorrect. I meant that the reference disappearing is rough because the Ptr doesn't increment the reference count.

I think that this is a special case in which you're busted because assigning to MemoryBlock.Ptr doesn't create a new reference. Consider another example where this could happen, beginning with a class CalendarEntry. Give it a property pDate as Double and get/set methods

Sub Date(Assigns d as Date)
me.pDate = d.TotalSeconds
End Sub

Function Date() as Date
dim d as Date

d = new Date
d.TotalSeconds = me.pDate
Return d
End Function

Now consider the following code.

dim theEntry as new CalendarEntry

If todayIsBeerDay then
dim d as new Date

theEntry.Date = d
End If
d.Month //oops...

So I don't think that the solution is to change the implementation of MemoryBlock.Ptr. Nor do I think that one should monkey with reference counting, including the timing of object destruction.

@Charles -- I agree, modifying .Ptr is a bad idea. There's just no decent way to handle the case. So I'm relegating it to a lesson learned, and hopefully others pay attention to my ramblings and learn from my mistakes. ;-)

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.