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