A VB6 app is experiencing a run-time errors at a variety of places.
I know this is the result of poor error handling, but is it possible to analyse the code to see where it is susceptible to run-time errors?
Any application is susceptible to run-time errors where there is no error handling around calls to external resources, so you could identify those points as a start.
I've used a free-tool (many years ago) that could retro-fit error handling to VB6 code, which would at least log errors and the point that they occurred.
Here it is: The HuntErr Addin for easy error handling in VB6
You need to make sure that every one of the methods (functions, subs, properties...) in your code base has an error handling statement. It's probably true that not every single one can generate a run time error, but that will protect the application from crashing without a lot of upfront analysis.
Make sure there's a statement before any executable line of code that says "On Error GoTo..." with a label, and then make sure to put that label with some error handling code at the bottom of the method. I've used a free tool called MZ-Tools 3.0 that allows you to automate the inclusion of this text. There is an Error Handler tab in the options that lets use specify what text you want to put in and where. This is what mine looks like:
On Error GoTo l{PROCEDURE_NAME}_Error
{PROCEDURE_BODY}
Exit {PROCEDURE_TYPE}
l{PROCEDURE_NAME}_Error:
LogError "{MODULE_NAME}", "{PROCEDURE_NAME}", Err, Err.Description
Then I just make sure that the LogError function exists and writes the error out to a log file that I can review.
Common sources of run-time errors in VB6 apps include
Accessing a key in a collection that doesn't exist
Calling a method or property on an object that is nothing
Using CLng to convert a string to a number when the string is null
Accessing an array beyond its length (like after calling Split and then assuming that the string has the number of pieces you expected)
So besides doing what others have suggested and analyzing where the actual errors are coming from, you could start by looking for areas such as these in your code and putting appropriate error handling around them. Keep in mind that often the best "error handling" doesn't involve using On Error at all, but preventing the error ahead of time by checking for these boundary case, like
If Not Object Is Nothing
If Len(string) > 0
If UBound(array) > x
etc...
There are some good answers here with both the On Error GoTo recommendations and the common errors that fall through the cracks that bwarner mentions.
But maybe widen the scope and utilize built-in tools to analyze code like breakpoints, watch expressions, and especially good for debugging run-time errors, the locals window (often overlooked in debugging, but very powerful) and the call stack. You can get a lot of great info on that from here: Debugging Your Code and Handling Errors
Other things to think about that may be helpful:
Invest in a tool that will help you
with the analysis like CodeSMART
2009 for VB6 or VB Project
Analyzer.
Try to port the existing application
to VB.NET - not to actually port and
use, but to view the conversion log
for things that need to be fixed.
Following Ryan's answer and the comment in response, you don't have to put error handling in every routine, just every Event and Sub Main() (and API callbacks if they don't already have it).
API callbacks refer to routines called directly by the Win32API, most often passed to Declared functions using AddressOf. (I.e. search your code for AddressOf and ensure all routines mentioned as arguments have error handlers that catch errors and do not allow them to attempt to bubble up.)
And I've just noticed this doesn't really answer the original question asked (although given the comment in response to Ryan's answer it is a good first step): Once you have error handling in every Event, etc, you will catch all errors, but you won't be able to directly analyse where all errors occur. You will need to extend the error logging to at least all the routines called by the events that log errors to more accurately locate the exact source of each error.
In VB6 a Runtime error occurs exactly when an Event function is called without error handling. So at least all your event handling functions (like Form.Open()) should be surrounded by an error handler (yes, I know VB6 does not have them), which can nicely be implemented like this (We do it in all our applications like this):
Use this as the first line of EVERY event handling function (it is a large valid label which sets the On Error at the end):
¦¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯: On Error GoTo ErrorHandler:
Now use this on the end of all those functions:
¦____________________________________________________________________________________________________________________________________: Exit Sub
ErrorHandler: handleError CodeDb, "Form_frm_filter_deletecolum", "cmd_deletecolum_Click", err.Number, err.description, err.Source, err.LastDllError, err.Helpfile, err.HelpContext, Erl: err.Clear
But replace the two strings with the module name and function name. AND replace each On Error Goto 0 with On Error Goto ErrorHandler.
Now create a function handleError with the given arguments (in my app it automatically sends a bug report into our bugtracking system), and display a nice error message.
We even pushed this a bit further, by having a prebuilt process that adds similar lines to all other (means non-event functions, or functions just called by other functions), to remember the line in which the error occured and to accumulate a complete stack trace (Yeah, stacktraces in VB6!). In addition this process adds line numbers to each line so they are given in the Erl part of the error handler.
Our tool is written as some MS Access modules, so I can't simply provide it for you, but you see where you have to go for it.
Related
I'm new to VBA in Access. I have set up a query that uses some VBA to requery on a combo box selection. This works great however every so often Access will randomly crash.
Is there any "if error" statement I can put in my VBA code that will pop up a message rather than completely crashing?
Error handling in your application is a separate issue from Access crashing. Even if you reach the low limits of Access (https://support.office.com/en-us/article/Access-2010-specifications-1E521481-7F9A-46F7-8ED9-EA9DFF1FA854), it shouldn't be crashing, but giving you an error message. That's a bug not of your doing. I've used Access for a while, and sometimes you have to work around the bugs, as well as the limits.
Sometimes, the VBA compiler fails to get things right after you make a change. If you Cut and Paste the text of an entire Module, it seems to force the compiler to recompile that whole Module. Always use the Debug > Compile menu item to compile and check your code. Then save.
Sometimes, doing the Compact and Repair will resolve problems. In fact, you need to Compact and Repair, because it seems that the file will bloat non-stop otherwise, and it has a 2GB limit. Do a backup first, because the Compact and Repair sometimes fails, leaving you with garbage.
Magisch gave an example of error handling in VB, which is something you should do to write a robust application (see http://www.vb6.us/tutorials/error-handling).
First of all, your an ordinary error shouldn't cause access to crash. If it does, you probably need to clean up your file some, for instance by repairing or de/re compiling it.
Secondly, if you want to handle errors in a simple way you can do it roughly like this:
Public Function doStuff (myInput as Integer) as Integer
On Error GoTo Error_Handling
'something that may cause an error to occur
Exit Function 'Important so your error handling doesn't get executed every time the function runs regardless
Error_Handling:
'something that you want to happen when the error occurs
End Function
First, apologies for the generic title - if anyone can suggest a better one, I'd be happy to change it, but right now, I have no clue where to start.
I have a workbook utilizing a DLL to access a data provider (Bloomberg), and the requirements to get it to work correctly are quite tricky. Furthermore, deployment is a nightmare, since users might need to reference the DLL themselves.
Naturally, I first check wether the library is referenced, before testing the library itself.
Here's my code (which is working as intended) :
Public Sub TestBloomberg()
Dim ref As Object
Dim fRef As Boolean
fRef = False
For Each ref In ThisWorkbook.VBProject.References
If ref.GUID = "{4AC751C2-BB10-4702-BB05-791D93BB461C}" Then
If Not ref.IsBroken Then
fRef = True
End If
End If
Next
If fRef Then
' In separate Sub to get around User-defined type error
Call TestBloombergConnection
ElseIf Not fRef Then
' warn user about missing reference
End If
End Sub
As you can see, if the reference to the DLL is set, I proceed checking if the library works as intended (this has a lot of external factors at play, such as wether the server-application is running, the user is logged in, etc.) You can think of this as a simple ON-ERROR-GOTO-wrapped call to the dll.
I am forced to move the actual test of the functionality to another sub, as called from the second if-block. If I have no (or a broken) reference to the dll, even though the library will not be called itself, I will get a User-defined Error. If I move the exact same code to another sub, it will work perfectly.
Finally, my question:
What happens when I run my VBA code, why do I get a (i think) runtime error during compile time? How can my code be so dependend on external factors, that it can't even get to the point of failing?
What this behavior demonstrates is that VBA compiles separate subroutines separately and at different times. I had a similar situation when I was trying to resolve references on behalf of the users (solving a versioning problem, which I got to work, but then abandoned as not worth the trouble).
When you are ready to enter a subroutine, it interprets only as much as it needs to, and you could get a compile time error then, even though to you it seems like you are at run time.
The error you are actually getting is probably a 429 Automation error or something similar. You would get that if you have a broken link (dll moved, deleted, or not registered). What I remember from my project, is that I could reliably handle it if a good reference was saved in the file, or no reference was saved, but if a bad reference was saved, the code had to be isolated similar to what you found. I think I had mine in a separate class, but the principle is the same.
I think this "interpret only as much as necessary" is considered a feature of VBA. You can run a certain section of code, even if you have compile errors elsewhere. This can be useful when you only have a partially written functions or other half-finished stuff, or if you open the file on a computer without some referenced software installed. It lets at least some of the functionality still work.
We are using visual studio. In case of try & catch we cant locate exactly the line of code for which error is thrown. Where as if we use resume, exact line is shown & we can make corrections there & test. Some times in testing environment reproducing error may not be possible in many cases. When error is thrown we have to atleast locate the error there itself. Further if procedure is a big one like having more than 400 lines then locating error without line of error is a big headache. When try catch is considered superior to on error statement, why is this feature not available? While we were using vb6 we could just type resume & check the error line. In vb.net we are searching for that feature.
Try this:
Go to the Debug menu.
Click on the the Exceptions... choice.
The following dialog should appear:
Note the Common Language Runtime Exceptions checkbox is checked.
Upon clicking OK, now when you debug your code anytime an exception is thrown by your code or the .NET Framework, the debugger will halt on the line that threw the exception. This makes finding where something is "breaking" much easier.
In VB6 the Err object is rather primitive, and gives you basic information about an error - a number and a message. "Error" state was easy to dismiss (On Error Resume Next) and proper error handling would obscure any method's intent.
.NET exceptions are more sophisticated. They're special objects that can actually contain every single bit of available information that's needed to locate an error - including the specific line of code that caused it.
That's because exceptions bubble up until they're caught (in a catch) block, and if they're not, they are unhandled and cause the program to stop. The exception will contain not only the type of error with a description and the very line of code that has thrown it, but also every single call that led to it - doing that in VB6 would require tremendous amount of meticulous "stack trace" building that could easily start lying, and wouldn't give you exact line numbers. .NET stack traces never lie.
To view the stack trace, you can place a breakpoint in any catch block and look at the exception's properties.
You can't resume like you would in VB6, because there could be dozens of method calls between the line that threw the exception and the line that catches it. But, like in VB6, you can move the yellow "current instruction" marker to another line and resume execution and re-run the try block line-by-line (F10) to see what's going wrong.
I've been mostly working with VB.Net for over a year and just noticed this
Am I going insane, or does VB.Net NOT have an "Unreachable code" warning?
The following compiles quite happily with nary a warning or error, even though there is a return between the two writeline calls.
Sub Main()
Console.WriteLine("Hello World")
Return
Console.WriteLine("Unreachable code, will never run")
End Sub
Am I missing something? Is there some way to switch this on that I can't find.
If not, is there a good reason for its omission? (i.e. or am I right in thinking this is a woeful state of affairs)
Forgive the air of rant about this question, it's not a rant, I would like an answer.
Thanks
I've raised this on MS Connect, as bug# 428529
Update
I received the following from the VB Teams program manager
Thanks for taking the time to report
this issue. The compiler has limited
support for this scenario, and as you
point out we don't have warnings for
unreachable code. There are some
scenarios that our flow analysis
algorithm does handle, such as the
following:
Sub Main()
Dim x As Integer
Return
x = 4
End Sub
In this case you'll get a warning that
x has never been assigned. For the
case you mentioned however we'll have
to look at implementing that for a
future release.
My guess is that it's an oversight in the compiler. Flow control is a very difficult problem to get correct in any language, but especially in a language like VB which has so many different flow control mechanisms. For instance,
Exceptions
Goto
On Error (Resume, Goto, etc ...)
Exit calls
If you feel strongly about this issue, please file a bug on Connect. We do take bugs filed via Connect very seriously and do our best to fix as many as possible.
They mention this in the following post:
https://stackoverflow.com/questions/210187/usage-statistics-c-versus-vb-net
See the last post.
I guess you could use FXCop to check your code instead or get a copy of Resharper from:
http://www.jetbrains.com/resharper/
I'd like to address Jared's answer.
Most of the issues he brings up are not problematic for data flow analysis.
The one exception is "On Error / Resume". They mess up data flow analysis pretty bad.
However, it's a pretty simple problem to mitigate:
If more than one "On Error" statement is used in a method, or the "Resume next" statement is used, you can just turn off data flow analysis and report a generic warning. A good one might be something like "On Error / Resume are deprecated, use exceptions instead." :)
In the common case of one only "On Error" statement and no "resume" statement, you can pretty much do normal data flow analysis, and should get reasonable results from it.
The big problem is with the way the existing DFA code is implemented. It doesn't use a control flow graph, and so changing it ends up being really expensive. I think if you want to address these kinds of issues you really need rip out the existing DFA code and replace it with something that uses a control flow graph.
AFAIK, you are correct that VB.NET does not give you a warning. C# does though.
Theoretically, the end user should never see internal errors. But in practice, theory and practice differ. So the question is what to show the end user. Now, for the totally non-technical user, you want to show as little as possible ("click here to submit a bug report" kind of things), but for more advanced users, they will want to know if there is a work around, if it's been known for a while, etc. So you want to include some sort of info about what's wrong as well.
The classic way to do this is either an assert with a filename:line-number or a stack trace with the same. Now this is good for the developer because it points him right at the problem; however it has some significant downsides for the user, particularly that it's very cryptic (e.g. unfriendly) and code changes change the error message (Googling for the error only works for this version).
I have a program that I'm planning on writing where I want to address these issues. What I want is a way to attach a unique identity to every assert in such a way that editing the code around the assert won't alter it. (For example, if I cut/paste it to another file, I want the same information to be displayed) Any ideas?
One tack I'm thinking of is to have an enumeration for the errors, but how to make sure that they are never used in more than one place?
(Note: For this question, I'm only looking at errors that are caused by coding errors. Not things that could legitimately happen like bad input. OTOH those errors may be of some interest to the community at large.)
(Note 2: The program in question would be a command line app running on the user's system. But again, that's just my situation.)
(Note 3: the target language is D and I'm very willing to dive into meta-programming. Answers for other languages more than welcome!)
(note 4: I explicitly want to NOT use actual code locations but rather some kind of symbolic names for the errors. This is because if code is altered in practically any way, code locations change.)
Interesting question. A solution I have used several times is this: If it's a fatal error (non-fatal errors should give the user a chance to correct the input, for example), we generate a file with a lot of relevant information: The request variables, headers, internal configuration information and a full backtrace for later debugging. We store this in a file with a generated unique filename (and with the time as a prefix).
For the user, we present a page which explains that an unrecoverable error has occurred, and ask that they include the filename as a reference if they would like to report the bug. A lot easier to debug with all this information from the context of the offending request.
In PHP the debug_backtrace() function is very useful for this. I'm sure there's an equivalent for your platform.
Also remember to send relevant http headers: Probably: HTTP/1.1 500 Internal Server Error
Given a sensible format of the error report file, it's also possible to analyze the errors that users have not reported.
Write a script to grep your entire source tree for uses of these error codes, and then complain if there are duplicates. Run that script as part of your unit tests.
I know nothing about your target language, but this is an interesting question that I have given some thought to and I wanted to add my two cents.
My feeling has always been that messages for hard errors and internal errors should be as useful as possible for the developer to identify the problem & fix it quickly. Most users won't even look at this error message, but the highly sophisticated end users (tech support people perhaps) will often get a pretty good idea what the problem is and even come up with novel workarounds by looking at highly detailed error messages. The key is to make those error messages detailed without being cryptic, and this is more an art than a science.
An example from a Windows program that uses an out-of-proc COM server. If the main program tries to instantiate an object from the COM server and fails with the error message:
"WARNING: Unable to Instantiate
UtilityObject: Error 'Class Not
Registered' in 'CoCreateInstance'"
99% of users will see this and think it is written in Greek. A tech support person may quickly realize that they need ro re-register the COM server. And the developer will know exactly what went wrong.
In order to associate some contextual information with the assertion, in my C++ code I will often use a simple string with the name of the method, or something else that makes it clear where the error occured (I apologize for answering in a language you didn't ask about):
int someFunction()
{
static const std::string loc = "someFunction";
: :
if( somethingWentWrong )
{
WarningMessage(loc.c_str(), "Unable to Instantiate UtilityObject: Error 'Class Not
Registered' in 'CoCreateInstance);
}
}
...which generates:
WARNING [someFunction] : Unable to
Instantiate UtilityObject: Error
'Class Not Registered' in
'CoCreateInstance