The very powerful exception function

Put simple Tips and Tricks that are not entire Tutorials in this forum
User avatar
nnnik
Posts: 4497
Joined: 30 Sep 2013, 01:01
Location: Germany

The very powerful exception function

09 May 2018, 14:50

Today I discovered something about the exception function that I didn't know before.
Prior to this day I didn't pay much attention to it. However today I read the documentation again and discovered something new about it.

AutoHotkey lacks a lot of things people want in languages. One example of this is its kind of limited features for throwing errors and functions like a printStack function.
And before I discovered this little feature in the exception function I thought it was impossible to do manually.
Helpfile wrote:Exception(Message [, What, Extra])

Creates an object with the following properties, also common to exceptions created by runtime errors:
•Message: An error message or ErrorLevel value.
•What: The name of the command, function or label which was executing or about to execute when the error occurred.
•Extra: Additional information about the error, if available.
•File: Set automatically to the full path of the script file which contains the line at which the error occurred.
•Line: Set automatically to the line number at which the error occurred.

If What is omitted, it defaults to the name of the current function or subroutine. Otherwise it can be a string or a negative offset from the top of the call stack. For example, a value of -1 sets Exception.What to the current function or subroutine and Exception.Line to the line which called it. However, if the script is compiled or the offset is invalid, What is simply converted to a string.
What is interesting and new to me about this function was the last sentence ( it seems I never properly read this sentence before I dont know why and it seems like many of you are the same so I will make a post here )
If you set the second parameter to a negative number you can throw more descriptive and accurate errors and even access the call stack.

A) More accurate errors:
If you ever wrote a function or class for someone else and used Throw to make them aware of errors you probably also used the exception function to write something like this:

Code: Select all

MyReuseableFunc( nr, min, max ) ;For this example our function will take a number and put it between the minimum and maximum numbers given to it
{
if !( ( nr|1 ) && ( min|1 ) && ( max|1 ) ) ;this checks if all parameters are numbers
	Throw exception( "At least one parameter is not a number" )
if ( min >= max )
	Throw exception( "min needs to be smaller than max" )
	;...
} 
And if you were to include this code like and you have an error inside your script somewhere:

Code: Select all

MyReuseableFunc( nr, min, max ) ;For this example our function will take a number and put it between the minimum and maximum numbers given to it
{
	if !( ( nr|1 ) && ( min|1 ) && ( max|1 ) ) ;this checks if all parameters are numbers
		Throw exception( "At least one parameter is not a number" )
	if ( min >= max )
		Throw exception( "min needs to be smaller than max" )
	;...
}

MyReuseableFunc( 10, 12, 13 )
MyReuseableFunc( 10, 12, 13 )
MyReuseableFunc( 10, 0x12, 13 ) ;error
Then your function will throw an error like:
Image
You might have noticed that in the error i acts as if the inside of the function is the problem - it doesn't point to the bad function call.
That's not very descriptive - if you have a function that you use a 100 times in a few 100 different places and you don't know which call caused the issue.

And here is where the exception function steps in or rather it's feature to go upwards in the stack:

Code: Select all

MyReuseableFunc( nr, min, max ) ;For this example our function will take a number and put it between the minimum and maximum numbers given to it
{
	if !( ( nr|1 ) && ( min|1 ) && ( max|1 ) ) ;this checks if all parameters are numbers
		Throw exception( "At least one parameter is not a number", -1 )
	if ( min >= max )
		Throw exception( "min needs to be smaller than max", -1 )
	;...
}
This solution is a lot smarter because our error message will now show us the bad call:

Code: Select all

MyReuseableFunc( nr, min, max ) ;For this example our function will take a number and put it between the minimum and maximum numbers given to it
{
	if !( ( nr|1 ) && ( min|1 ) && ( max|1 ) ) ;this checks if all parameters are numbers
		Throw exception( "At least one parameter is not a number", -1 )
	if ( min >= max )
		Throw exception( "min needs to be smaller than max", -1 )
	;...
}

MyReuseableFunc( 10, 12, 13 )
MyReuseableFunc( 10, 12, 13 )
MyReuseableFunc( 10, 0x12, 13 ) ;error
Image
Use this to make your errors more accurate.
You can even add a -2 if the error probably lies 2 calls above your current function and many other things.


B) Print Stack:
Printing the stack is very important information when debugging difficult bugs.
You might have 50 functions calling 20 functions which then in turn also call 1 function.
And under rare specific circumstances an error occurs while calling that 1 function.
And no matter how good you make your throws you will probably never get the entire story with just that - so you start to debug manually or with break points.
That just takes hours and you might even miss the specific point where the error occurs.
So you start to work with MsgBoxes to see which part executes and which doesn't to see where exactly you are when the error occurs.
That also takes time. It would be much easier if you could see where you are when the error occurs meaning could seeing which code called which code etc.
In many languages a printstack function is used for exactly that. However in AHK we do not have such a function.
So what we do know is built our own:

Code: Select all

printStack( offset := -2 ) { ;turns the results of the generateStack function into human readeable text
	stack := generateStack( offset )
	str := "Stack Print, most recent calls first:`n"
	for each, entry in stack
		str .= "line: " . entry.line . "`tcalling: " . entry.what "`tfile: " entry.file "`n"
	return str
}

generateStack( offset := -1 ) { ;returns the stack as an Array of exception objects - the first array is the function most recently called.
	if ( A_IsCompiled )
		Throw exception( "Cannot access stack with the exception function" )
	stack := []
	While ( exData := exception("", -(A_Index-offset)) ).what != -(A_Index-offset)
		stack.push( exData )
	return stack
}
This can display the stack using the exception function.
It's especially useful for cases where you have a seemingly ranom bug appear in a commonly used function like this:

Code: Select all

fn5()
fn4()
fn3()
fn2()
fn1()


fn5() {
	fn2()
	fn4()
}

fn4() {
	fn1()
	fn2()
}
fn1() {
	fn3()
	fn3()
}

fn2() {
	fn3()
	fn3()
}

fn3() { ;causes bugs randomly
	Random, out, 0, 15
	if !out {
		Msgbox % printStack()
		Throw exception( "Some Error occured" )
	}
}

printStack() {
	stack := generateStack( 2 )
	str := "Stack Print, most recent calls first:`n"
	for each, entry in stack
		str .= "line: " . entry.line . "`tcalling: " . entry.what "`tfile: " entry.file "`n"
	return str
}

generateStack( offset := 1 ) {
	stack := []
	While ( exData := exception("", -(A_Index+offset)) ).what != -(A_Index+offset)
		stack.push( exData )
	return stack
}
Running this code will either do nothing or show a MsgBox with some text followed by an error.
Whats important is the content of the MsgBox - it will tell you a lot about where the error happened.
Image
In this case the error appeared when fn2 called fn3 for the second time.

Image
In this case the error appeared when fn4 called fn1 whose first call to fn3 caused the error.

Image
In this case the error appeared when fn5 called fn2 whose first call to fn3 caused the error.
( This is also the very first place an error could happen. )
Recommends AHK Studio
User avatar
Masonjar13
Posts: 1553
Joined: 20 Jul 2014, 10:16
GitHub: Masonjar13
Location: Не Россия

Re: The very powerful exception function

09 May 2018, 18:38

While bugs happen infrequently for me (I'm kind of a perfectionist, I suppose), your stack functions are great! I'll definitely be using them, thanks :thumbup:
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
User avatar
kczx3
Posts: 1502
Joined: 06 Oct 2015, 21:39

Re: The very powerful exception function

09 May 2018, 19:54

Very nicely done!
User avatar
nnnik
Posts: 4497
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: The very powerful exception function

10 May 2018, 03:06

I'm glad you like it - though this is just a showcase of how to use this technique not a full function.
Thats also the reason why I'm not sharing them in the Scripts forum.
If someone could write a proper function I would be very grateful.
Recommends AHK Studio
Helgef
Posts: 4663
Joined: 17 Jul 2016, 01:02
Contact:

Re: The very powerful exception function

10 May 2018, 05:47

Call stack is very useful information.
If someone could write a proper function I would be very grateful.
It looks proper to me, similar to what I have been using. I never needed to collect recursive calls, that is the only thing missing I guess. I'm not sure line numbers display correctly from #included files, I do not recall though.

Cheers.
guest3456
Posts: 3328
Joined: 09 Oct 2013, 10:31

Re: The very powerful exception function

10 May 2018, 07:32

this is a great tutorial, and one i wish i've had before.. some bugs are a pain to find. this does require a little bit of overhead in designing your functions and thinking ahead of the possible errors to throw. but probably saves more time in the end

User avatar
Masonjar13
Posts: 1553
Joined: 20 Jul 2014, 10:16
GitHub: Masonjar13
Location: Не Россия

Re: The very powerful exception function

10 May 2018, 20:50

nnnik wrote:If someone could write a proper function I would be very grateful.
I agree with Helgef. I don't see why your rendition would be considered improper.
guest3456 wrote:this does require a little bit of overhead in designing your functions and thinking ahead of the possible errors to throw
I usually write the function/code first, run it, try to do weird things to break it, and if any of them do, compensate. If they can't be compensated for, then I'll fallback to an error message. You should see the kind of error checking I have for a class that interacts with my IEObj class.. 2-3 pre-written error messages for each method, and that's after compensating. Good chance I'll replace all those with this now, since the actual error gives more detail.
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
User avatar
nnnik
Posts: 4497
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: The very powerful exception function

16 May 2018, 03:17

This is going to be great for OnError from the new test build.
Recommends AHK Studio

Return to “Tips and Tricks”

Who is online

Users browsing this forum: No registered users and 3 guests