Saved by PowerShell


Recently I made a very large update to our code base.  Our code base lacked a standard way of guarding entry and exit points into the various components.  Having said guards is useful for error handling, tracing, reducing redundancy, etc …  The edit standardized our entry points by adding start/end macros to our entry point functions.  In addition to other house keeping, the macros also created an HRESULT variable named "hr".  Example below.

#define MY_ENTRY_GUARD() HRESULT hr
#define MY_ENTRY_EXIT() return hr

Ran suites, everything passed, checked in.  Then I got an email from another dev who spent some time tracking down a bug related to this check-in (sorry Calvin).  He discovered one scenario my fix did not take into account.

SomeMethod
{
  MY_ENTRY_GUARD();
  if ( somecondition ) {
    HRESULT hr = E_FAIL; // shadows the first hr
  }
  MY_ENTRY_EXIT(); // returns unmodified hr
}

The double declaration of the variable "hr" is not an error or even a warning in C++.  Instead the inner "hr" shadows the outer and hence the rest of the method doesn’t update the "hr" which is actually returned.  So now I had to find every place in this change where a nested hr was declared.  Did I mention this edit was huge?  Going through by hand would not only be time consuming, it would also be very error prone. 

At first I considered parsing out the C++ and doing basic brace matching to look for shadowing "hr" variables.  I ruled that out due to the amount of time I would need to invest in the script to take into account comments, string literals, etc …  Really I didn’t need brace matching, I really just needed to know when I entered and left a method.  Almost all C++ methods have their opening and closing braces on the first column.  Writing a script to detect this is trivial. 

Script took about 5 minutes to write and 10 to run in the code base.  Saved me countless hours of error prone reviews.  Thank you PowerShell.

Find-DoubleHr.ps1:

param ( $argFileName = $(throw "Need a file name") )

function Do-Work() {

    $i = 0

    foreach ( $line in (gc $argFileName) )  {

        new-tuple "Text",$line,"LineNumber",$i

        $i++

    }

}

function Do-Parse() {

    begin {

        $inMethod = $false

        $seenHresult = 0

        $seenMacro = 0

    }

    process {

        $tuple = $_

        if ( $inMethod ) {

            switch -regex ($tuple.Text) {

                "^}" {

                    $inMethod = $false

                    if ( ($seenHresult -ne 0 )-and ($seenMacro -ne 0) ) {

                        "Found a double {0},{1}" -f $seenHResult,$seenMacro

                    }

                    $seenHresult = 0

                    $seenMacro = 0

                    break

                }

                ".*MY_ENTRY_GUARD.*" {

                    write-debug ("Macro: {0} " -f $tuple.Text)

                    $seenMacro = $tuple.LineNumber

                    break

                }

                "HRESULT.*\Whr\W" {

                    write-debug ("HResult: {0}" -f $tuple.Text)

                    $seenHresult = $tuple.LineNumber

                    break

                }

            }

        }

        elseif ( $tuple.Text -match "^{" ) {

            $inMethod = $true

        }

    }

}

"Processing $argFileName"

Do-Work | Do-Parse

Comments (0)