torsdag 3 december 2009

A replacement for C at last?

Wow, those Google guys sure keep spitting out new products. Barely had I finished my first shaky \/\/ave message when to my surprise I found out they have done what I have secretely dreamt of for the past five years - without getting round to it, obviously - they have created a new low-level language. For us poor souls stuck in C/C++ drudgery, this is amazing news. I'ts been over 20 years since something on this magnitude happened on the "system languages" frontier. Not counting Ada 95, nothing has happened as far as low-level languages are concerned.

Now, while you "web" guys might not hold a new language high in esteem - after all you are very badly spoiled - you get a new language complete with a framework and an IDE every other year: Ruby on Rails, PHP, Rebol, or what have you. But a new language that tackles the actual machine, now that's something you don't see every day. Where you can write stuff that ... wait for it ... behaves differently depending on the computer architecture. Where bit-shift operations give different outcomes. Now that's hardcore.

Of course I just had to dive right in and have a look at Google Go. I was scrolling through the FAQ and Tutorials, trembling with enthusiasm to see all the hideous 80's constructs that this language just about might pass on to history's scrapheap. And I can say that it definitetly looks better than C, even better than C++. (Well, honestly, how hard could it be to beat those langugages?). Built-in strings and maps. Good. Garbage collection. About time. Away with the preprocessor. Even better - even C# failed somewhat at this. But for some strange reason they abolished exceptions. Which is something that has puzzled me for a long time. Apparently Google resent exceptions. In their C/C++ style guide they supply a long list of reasons why exceptions suck. But they all seem to be either very subjective, biased or plain-out "duh" moments. Most notably the last one:

"The availability of exceptions may encourage developers to throw them when they are not appropriate or recover from them when it's not safe to do so"

Duh! You could say the same about any other "advanced" languge construct. Inheritance, anyone? I've seen OO challenged developers screw up simple type hierarchies and creating a mess 100 times worse than you could ever screw up a catch-throw. It seems the bottom line is Google just don't feel comfortable with exceptions, period. So we probably will not see them in Go any time soon. It's a pity, I find programming without exceptions an arduous task and a code cluttering no matter how you do it. Let's hope they find a way to implement exceptions to their liking.

fredag 4 september 2009

Copy-paste in X - 22 years and still not working.

Being tired of spending 5-10 hours per week poking things you take for granted to make them work, I recently ditched the Linux on a laptop idea and got a mac. Haven't looked back, although there are some grave annoyances in the interface between the slick OS X and the applications from the Unix world. Clipboard is one of those. No matter how hard I punced Ctrl-V like you're supposed to, I did not get it to work. All my colleagues gave me that look when I asked them about my problem. 'Well - um - you do know that you shouldn't use cmd-V, right?'. Duh! That wasn't problem. After spending hours sifting through discussion boards and blogs telling me exactly that same thing, I finally found a friendly person who would actually acknowledge that there was a problem here. A big hand for D. W. Hoard:

Be sure to read the Note added on 06/29/09!

onsdag 19 augusti 2009

Error handling - Internal_error_handler to the rescue

In the beginning, error handling in MySQL code was a straightforward process. There was your C function my_error(int nr, int MyFlags, char* format, ...)that would simply put the error on a queue for you, ready to be returned to the client at the right time. The two last arguments work just like printf. You can easily check whether the current thread threw an error by reading THD::is_error(). It is very simple and very hard to get wrong.

Later down the line, however, the need has surfaced for something more. If module A asks module B to do something as part of the execution of module A, module B might set a critical error since this would be the right thing to do had it not been part of A's execution. Then later A might call on module C to do something, but there might be code inside C that is not being executed because it notices that somewhere something went wrong and bails out.

An example (from Bug#35996):
When the user issues SHOW CREATE VIEW, it might be that the view references non-existing tables. Why this is allowed is another discussion, beyond the scope of this post. There must also be an access check. The user has to have SELECT and SHOW CREATE privilege on the view, and SELECT privilege on all table columns that the view references.

The execution of SHOW CREATE VIEW will call on the check_access procedure, which recurses through the view itself and all tables or views that it references. This procedure is wired to raise an error for non-existing tables, and for columns that the user has no privileges an error will be raised saying

  • Table 'test.t1' doesn't exist, or
  • "... command denied to user ... for column ... in table ..."

The latter error must be hidden by the SHOW CREATE VIEW execution since it gives out information about tables that the user has no right to know. Remember, the view may just do a SELECT *, but this error will actually name the columns for which the user is not authorized. The error must be replaced by the more anonymous

"View ... references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them".

This is accomplished by examining the current error in the queue, then removing that error and replace it by the other one. . Recipe for disaster? Read on. Remember that SHOW CREATE VIEW allows the user to see the definition (and for the view to exist) of a view referencing columns or even tables that don't exist. Hence it must manipulate the errors once again. The same message may be used, but the status must be warning level and not error level. A message with an error status has the authority to stop the transfer of information to the client, while a warning will simply pop up after the wanted result is transferred. The problem is, by simply looking at the anonymous error message we don't know what the original cause is:
  • If it was a missing column, all is fine, let's downgrade an error to a warning an get on with it.
  • If it was a column that the user has no right to know about, there must be an error.
What to do?

Obviously, it is bad design to have functions putting up errors only so that other functions' execution paths depend on what errors are on the queue. You need to know why that error is there in the first place and what the circumstances were when it was put there. In other words, you need to be there when it happens.

Enter Internal_error_handler. This brilliant invention is a hook for whenever an error is raised and a caller may install such a hook before calling a function. This way an appropriate action may be taken so that the final error message may be set on the spot. It does this using a virtual method handle_error. (handle_condition in 6.0) Bug#35996 was fixed by making SHOW CREATE VIEW, prior to calling check_access, install an Internal_error_handler that would:
  • If the view itself was access checked when the error was raised, do nothing
  • If a column or a table was access checked when the error was raised, hide the details of it.
  • If the error was raised due to a missing column or table, hide the details of it and make it a warning.
Now is the time to take this approach one step further. The code that anonymized error messages is (for some reason) put in TABLE_LIST:
void TABLE_LIST::hide_view_error(THD *thd)

Post-bug#35996-fix the function is only called once, and in an awkward way. The fix to replace the code is trivial with lessons now learned. But a great amount of fragile code can get removed, spanning tens of files, so I will list the details below. Whenever a command is executed out on a view (e.g. SELECT, INSERT etc) referencing either non-allowed ot invalid columns/tables/databases, the error message gets anonymized. This is accomplished by a C-style function pointer in Name_resolution_context called process_error. This function constitues a hook for TABLE_LIST::hide_view_error. The hook is called from various places in the name resolution code, probably only the ones that happen to be passed in most current scenarios. Most likely you can find a number of bugs here. The hook is installed in mysql_make_view, however.

All of this code can be removed and replaced with the more general Internal_error_handler. The benefits are obvious: We replace an unsafe C pointer with polymorphism, we substitue a general approach for an ad-hoc one, we remove unrelated functionality from TABLE_LIST and we don't have to sprinkle the code with easily-forgettable calls to the process_error hook.