2

Take this code:

def A():
   try:
      B()
   except Exception:
      pass

def B():
   C()

def C():
   print exception_handling_pointer()

A()

The function exception_handling_pointer should return me a pointer to the function where this specific exception would be checked first for being handled. I.e., in this case, I would expect the output to be sth. like:

<function A ...>

How can I implement the function exception_handling_pointer?

Albert
  • 65,406
  • 61
  • 242
  • 386

2 Answers2

3

You can't decide where a Exception will get handled without actually raising the Exception. This is easy to see here:

try: 
    raise input('Raise which?')
except input('Catch which?') as e: 
    pass`

Any function that does what you want would have to predict user input here. The whole endeavor is futile and Python has no support for it.

Anyways, i hope you ask only out of interest ...

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • Ouch, that's an evil example. I second the last paragraph - code doing such a thing would be *sooo* broken. –  Nov 28 '10 at 18:09
  • What I meant is if there is any way I could get access to the exception-longjmp-table which tells Python where to jump at when some exception occurs. – Albert Nov 28 '10 at 19:40
  • Or isn't there such and it is all stored in each stack frame and it goes them up and checks if there are any `except` blocks in each of them? If so, is there a way I can get the first stack frame which has such an `except` block? – Albert Nov 28 '10 at 19:42
  • You are right. But I also didn't really wanted to know in advance where it would get handled. I was mostly interested in the first function where it could get handled because it has some `except` blocks. I changed my question a bit to make this more clear. – Albert Nov 29 '10 at 03:42
2

This is a pretty silly thing to do and most people would say that it can't be done (THC4k gives compelling evidence of this for the general cace) but it does sound fun and should be perfectly doable in many real use-cases.

step 1. You need to step back through the frames. Get the first one with sys._getframe or inspect.currentframe (don't tell anyone, the second seems aliased to the first). Then you can iterate through them with f.f_back

step 2. Each one will have a f.f_lasti instruction. This is the last instruction that was executed in the frame. You'll have to save it. Now go backwords through the bytecode - f.f_code.co_code - and look for a SETUP_EXCEPT opcode with an argument that jumps to after f.f_lasti`. The jump point is the exception handling.

step 3. This is where it gets fuzzier. The key is that the actual comparison operation will be a COMPARE_OP with a 10 as its argument. In all cases that I've seen, it's followed by a POP_JUMP_IF_FALSE. This will jump to the next except clause or the finally clause. It will be preceded by the code that loads loads the exceptions onto the stack. If there is only one, then it will be a straight LOAD_GLOBAL or a LOAD_GLOBAL or LOAD_FAST (depending if the module with the exceptions is global or local) followed by a LOAD_ATTR. If there are multiple exceptions being matched then there will be a sequence of load operations followed by a BUILD_TUPLE (idiomatic) or BUILD_LIST (some other weird or non-idiomatic situation).

The point is that you can go through the LOAD_X instructions and compare the name to the exception that you're matching. Note that you're comparing name only. If they've reassigned the name, you're SOL.

step 4. Let's assume that you found a match. Now you need the function object. The best way that I can think of to do this follows (I reserve the right to update): The f.f_code will have a co_filename attribute. You can loop through sys.modules and each one will have __name__attribute. You can compare the two keeping in mind that you should use __name__.endswith(co_filename). When you get a match, you can loop over the modules functions and compare their f.func_code.co_firstlineno attribute with the frames f.f_lineno attribute. When you get a match, you have your function. You should loop over the methods of each class in the module as well. There's the possibility that the handling is occurring in some nested function in which case, I can't currently think of a sensible thing to do. (It would be a whole other bytecode hack and would itself be flakey)

step 5. Profit.

This should give you the general idea of how to go about doing this. There are all sorts of corner cases where you wont be able to do it but in any normal use-case, you should be able to pull it off. If you write code that depends on being able to do it, it will break though. This is sort of "Do it because I can" sort of thing.

aaronasterling
  • 68,820
  • 20
  • 127
  • 125
  • Thanks, this seems like one way to do it. But I am curious: Is this the way Python itself is doing it? Because it seems pretty inefficient and complicated to me. I would expect that the `SETUP_EXCEPT` op actually saves that information somewhere. Wouldn't it be easier to just read that somehow? – Albert Nov 28 '10 at 23:56
  • @Albert. This is not the way python does it. If you look at the assembly listing for your example, you'll see how python does it. It doesn't store any tables anywhere that I'm aware of. It uses the `SETUP_EXCEPT`, `SETUP_FINALLY` and `COMPARE_OP` along with various `*JUMP*` instructions. So python actually just runs the opcodes. Here, we have to examine them which is where the extra complexity comes from. Python doesn't store any tables with this stuff in it as far as I know. For example, how would the compiler create a table for THC4k's example? – aaronasterling Nov 29 '10 at 00:27
  • The more I think about it, the more positive I am that there isn't a table with this information in it anywhere. I don't see how one could be built. The compiler has no idea what exceptions are being excepted. They're just symbols with scope information as far as it's concerned. – aaronasterling Nov 29 '10 at 00:29
  • Yes but what I meant is that `SETUP_EXCEPT` actually does *something* and not nothing. So it may not store a table but at least a pointer (the argument). I'm quite sure that Python does not need to search for the last `SETUP_EXCEPT` when an exception occurs but that it has stored this pointer somewhere on the stack. And that is what I meant: Is there any way to get that information directly? I guess not because I haven't seen any. But maybe I really should check out the CPython source. – Albert Nov 29 '10 at 03:37
  • @Albert. `SETUP_EXCEPT` puts the except block on the 'block stack' for the frame in which it occurs. I don't know how to get this from python but at any rate, you can just read the address of the except block straight out of the byte code and jump right to it. This is not the complicated part. The complicated part is going to be parsing which exceptions are being loaded and checking if your is in the list. But that's not complicated either. I'll probably have some working code tonight. – aaronasterling Nov 29 '10 at 03:58
  • @Albert. I finally got motivated and have some working code. It's capable of getting the function if it's a module-level function or method on a class that is either at module level or nested in one that is. Getting the actual function object is the most expensive part. Some of the details that I posted were a little off (I'll fix it when I get a moment). Did you end up figuring something else out? If so, would you at least give a hint? I'll comment my code and post it when I get a chance. The biggest improvement to be made is in getting the function object. – aaronasterling Dec 01 '10 at 23:23
  • Thanks really for your effort. I didn't really figured something else out about how to solve my particular question. But I rather thought about some other way where I probably can work around this. But I am still very curious about your solution for learning purpose. – Albert Dec 02 '10 at 13:56
  • @Albert. I was just about to delete this answer :) There's enough wrong with it that I don't want to leave it up. I'm still hacking on my solution (I've found problems with it) out of my own interest and I'll edit this answer when I'm done. It's going to be about 300 lines of code and will have serious unavoidable shortcomings. `except Myexception if False else IndexError` will give a false positive that I don't know what to do about for instance. This is definitely not an acceptable answer ;) THC4k's answer is much better. In case you don't know, it would be possible to change your selection. – aaronasterling Dec 05 '10 at 21:15