Rubberduck: Code Inspection for undimensioned arrays

Created on 17 Feb 2016  路  5Comments  路  Source: rubberduck-vba/Rubberduck

Dynamic arrays need to be dimensioned before they can be used.
User defined Types (structs) can include dynamic arrays, and can cause crashes if passed to Win32 functions without first being dimensioned.

difficulty-03-duck enhancement feature-inspections up-for-grabs

Most helpful comment

Stumbled across this, and thought I'd throw this out - ReDim isn't the only way to initialize an array. The inspection would also have to check for array returning functions. I use this paradigm _all the time_:

Dim Foo() As String
Foo = Split(vbNullString)

Not to mention to ubiquitous:

Dim cells() As Variant
cells = Range("A1:C3")

All 5 comments

So... should this inspection only look at DeclarationType.UserDefinedTypeMember declarations that are arrays never used in a ReDim statement?

_Any_ dynamic array will cause a runtime error 9 (subscript out of range) if it's used before it's dimensioned. This is a very useful inspection, that should default to error level (because it catches a _runtime_ error).

This code would trip the inspection:

Sub foo()
    Dim a()
    a(1) = 42 'boom
End Sub

This code too:

Type T
    B() As Variant 'interestingly, not specifying a type here is a compile error
End Type

Private bar As T

Sub foo()
    bar.B(2) = 42 'boom
End Sub

Fix for snippet 1:

Sub foo()
    Dim a()
    ReDim a(_TODO_)
    a(1) = 42 'boom
End Sub

Fix for snippet 2:

Type T
    B() As Variant 'interestingly, not specifying a type here is a compile error
End Type

Private bar As T

Sub foo()
    ReDim bar.B(_TODO_)
    bar.B(2) = 42 'boom
End Sub

Note that both quick-fixes generate compile errors - this is by design, and turns a runtime error into a compile-time validation, which is a major improvement. The _TODO_ token is an illegal identifier, so there can't be any clashes with an existing variable - but the Rubberduck parser shouldn't break over it, since the IDENTIFIER lexer token rule has been relaxed lately and won't blow up over an identifier that starts with an underscore.

Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Private Type TDynamic
  a As Byte
  b() As Byte
  c As Byte
End Type

Sub Mysub()

  Dim o As TDynamic
  Dim iLength As Long

  iLength = Len(o) ' = 5

  o.a = 1
  o.c = 3
  'This line will run (as long as o is not expanded in Locals, regardless of whether Locals is visible)
  CopyMemory o, "abcde", Len(o)

  'Expanding o in the Locals window, Excel goes BOOM

  'This works
  Debug.Print o.a ' = 164!

  'This works
  Debug.Print o.c ' = 3

  'Don't hover over o.b
  Debug.Print o.b 'Excel goes BOOM

  'Stopping the code, or exiting the procedure, Excel goes BOOM
End Sub

Actually, the dynamic member of a type, seems to be a pointer, so while it is possible to ReDim the member, the size of the type doesn't change. That makes it unsafe to use with CopyMemory, regardless of whether you Redim the dynamic array.

Private Type TDynamic
  a As Byte
  b() As Byte
End Type

Sub Test()
  Dim td As TDynamic

  td.a = 1
  Debug.Print Len(td) '5

  ReDim td.b(4)
  Debug.Print Len(td) '5

  ReDim td.b(400000)
  Debug.Print Len(td) '5

End Sub

Stumbled across this, and thought I'd throw this out - ReDim isn't the only way to initialize an array. The inspection would also have to check for array returning functions. I use this paradigm _all the time_:

Dim Foo() As String
Foo = Split(vbNullString)

Not to mention to ubiquitous:

Dim cells() As Variant
cells = Range("A1:C3")

Not to mention the Array function...

Sub TesticlePop()
  Dim foo() As Variant
  foo = Array(1, 2, 3)
End Sub
Was this page helpful?
0 / 5 - 0 ratings

Related issues

retailcoder picture retailcoder  路  3Comments

Hosch250 picture Hosch250  路  3Comments

DecimalTurn picture DecimalTurn  路  3Comments

ChrisBrackett picture ChrisBrackett  路  3Comments

eteichm picture eteichm  路  4Comments