
|
To throw or not to throw, that is the question.
I have a real world example of a function that I am up in air about as far as exceptions are concerned.
Take the following signature with an intentionally blank return value...
connect(String IPAddress);
The follow errors can occur...
Malformed Address ie: "1.2.3"
Can not connect ie: no route to server
Stack overflow because the length of the string is 100 megabytes. (think reading the string in over a socket. This is a common way to exploit a server).
Should any case throw? If so which ones...
christopher baus (tahoe, nv)
Thursday, October 16, 2003
In my previous example assume C++ and the String class has value semantics..
For example the string contents will be copied when the temporary is created when the function is called, hence the stack overflow.
christopher baus (tahoe, nv)
Thursday, October 16, 2003
Part of the problem is that the connect function is doing too much. It's parsing addresses from strings and validating them and it's also then taking the address and making a connection to it. To me that functionality is unrelated. I'd split that up.
So now we have
CAddress address(IPAddress);
thing.connect(address);
It hasn't reduced the errors that are likely to occur but it's split them into address related errors and connection related errors.
So, how do we deal with the address related errors. Firstly I'd pass the string by const reference, this removes the potential for a stack overflow if the string is copied by value and it's huge. It doesn't solve the problem of dealing with a huge string but it means we can control the problem rather than letting the stack get blown...
Now we can validate the address in the address class. Personally I'd throw an exception from the constructor if the address string isn't valid. The alternative is allowing address objects to be constructed that can be 'invalid' and having an isValid() function to determine if this particular instance of an address object is one we can connect to. I don't like the alternative. I'd also add a static function to the address class that can tell me if a given string IS a valid address. If you call CAddress::IsValid() and it returns true then the constructor wont throw if you pass it the same string. If you're doing user interface work you might want to use CAddress::IsValid() in your validation. Way down at the point where we use the address we expect it to be valid and if it's not an exception will occur.
The validation deals with both the 1.2.3 error and the 'too big' error.
So now we have a connect function that can fail due to network issues, how does it report these?
I need more context.
Is the connect function on a connection (socket) object? or is it somewhere else? The reason I need to know this is that if it's on a connection object then to be able to call it a connection must already exist, this means that you're happy for an unconnected connection object to exist so perhaps we'll just return true if we connect and provide a way of querying an error code form the connection object. If it's not on a connection object then perhaps you don't want to allow the concept of a disconnected connection, so perhaps it returns a connected connection if the connect call succeeds and throws an exception if it doesn't...
Len Holgate (www.lenholgate.com)
Friday, October 17, 2003
> Part of the problem is that the connect function is doing too much.
That was my thought. Just wanted to see what other's thought. You often see APIs which do both though.
>CAddress address(IPAddress);
thing.connect(address);
>So, how do we deal with the address related errors. Firstly I'd pass the string by const reference.
I agree with that, I was more curious of how people consider the stack overflow exception vs invalid data. One is part of program logic, the other a the result of a hardware limitation, or simply the fact that computers are finite machines.
I got in a heated discussion about this on comp.lang.c++.moderated. My feeling is that calling a function, even if it does nothing, should not be considered a no throw operation. On most modern operation systems the failure to increase stack is vary similar to the failure of new. Both, in my opinion, are very difficult to recover from, and for most pratical purposes are fatal conditions.
> Now we can validate the address in the address class. Personally I'd throw an exception from the constructor if the address string isn't valid.
The one thing I don't like about this is exceptions are now required in normal program flow. You are using exceptions to do simple data vaildation. I'm not sure if this really the intention of the exception mechanism, but it is common in libraries such as Java's.
>So now we have a connect function that can fail due to network issues, how does it report these?
> I need more context.
Ok I'll assume that it doesn't create a socket object, it just connects, then disconnects, for a say "are you there" type procedure.
Here's your solution...
class IPAddress{
IPAddress(CString address){...}
...
};
void connect(IPAddress address);
void OnOk()
{
// get the IP address from the user field and put it in
// CString IPaddress, ok I'm assuming MFC here.
CString IPaddress;
try{
IPAddress address(IPAddress);
connect(address);
}
catch(InvalidIPAddress e){
AFXMessageBox("Sorry you didn't enter a valid IP");
}
catch(ConnectFailed e){
AFXMessageBox("no one is home");
}
}
I wish I could think of a way to bring the stack overflow situation more into the equation, just to make the point.
Stack overflow is a serious problem, and a REAL exception. In my opinion ignoring the length of unbounded data structures such as strings is one of the major security problems today. The C++ community has done a poor job addressing this issue in my opinion, and perpetuates the problem by blindly ignoring the issue of stack overflow and over run, calling it an implementation detail. Well in Win32 you end up with an SEH, which many programmers translate to C++ exceptions. So in fact the act of calling a function, for all pratical purposes on Win32 CAN throw an exception. It doesn't matter if the C++ gurus blame this on MS or not, it is still a problem that real world programmers need to address...
I've been working on a paper which discusses this issue at length, and I will post it shortly to my web site shortly.
christopher baus (tahoe, nv)
Friday, October 17, 2003
>The one thing I don't like about this is exceptions are now required in normal program flow. You are using exceptions to do simple data vaildation.
No I'm not, I said I'd provide a static that did a validation pass on the data. So if you wanted to do user validation you could. The point is that if you as a programmer choose to create an address object from a string you'll only get one if the string is valid. I've provided you with a get out, call the check function first. If you think that's wasteful (calling a validation function before a conversion function) simply make a static factory function that does the validation and returns a dynamically allocated address if the address is valid. Personally I wouldnt take this route as you then end up with a memory management issue that you can avoid...
So, if we're way way up in user interface code and we're doing all this then it would probably look like this:
void OnOk()
{
// get the IP address from the user field and put it in
// CString IPaddress, ok I'm assuming MFC here.
CString IPaddress;
if (IPAddress::IsValid(IPAddress))
{
IPAddress address(IPAddress);
If (!connect(address)
{
// error
}
}
else
{
// errror
}
But if we're down in the depths of a library then we might reasonably assume (given we've provided the appropriate docs) that the user would have ensured that the address is valid. So we wouldnt bother with the validation call.
Either way, the point is that by the time we have a valid address object in our hands we KNOW that it's valid. We can do all the things we might want to do with an address with it...
:)
So, the stack overflow. Personally I'd suggest not getting in a position where you're using that much stack ;) (left as an excercise for the reader)
Given we have classes to manage dynamic memory for us we can be a bit more relaxed about dynamic allocations. Yeah, I know it's probably more efficient to extend the stack, but that's a risk vs performance trade off. You can decide which to take...
I've been out on the champagne and probably shouldn't be posting, so, be kind...
Len Holgate (www.lenholgate.com)
Friday, October 17, 2003
Recent Topics
Fog Creek Home
|