M PyShell.py
M rpc.py

SF Bug 676398 Doesn't handle non-built-in exceptions
1. Move exception formatting to the subprocess; allows subclassing of
   exceptions, including subclasses created in the shell without
   introducing excessive complexity in the RPC mechanism.
2. Provide access to linecache from subprocess to support this.
diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py
index cbfd203..98e0918 100644
--- a/Lib/idlelib/PyShell.py
+++ b/Lib/idlelib/PyShell.py
@@ -353,6 +353,7 @@
         self.rpcclt.register("stdout", self.tkconsole.stdout)
         self.rpcclt.register("stderr", self.tkconsole.stderr)
         self.rpcclt.register("flist", self.tkconsole.flist)
+        self.rpcclt.register("linecache", linecache)
         self.transfer_path()
         self.poll_subprocess()
 
@@ -404,23 +405,6 @@
                 if what is not None:
                     print >>console, `what`
             elif how == "EXCEPTION":
-                mod, name, args, tb = what
-                print >>console, 'Traceback (most recent call last):'
-                exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py")
-                self.cleanup_traceback(tb, exclude, console)
-                traceback.print_list(tb, file=console)
-                # try to reinstantiate the exception, stuff in the args:
-                try:
-                    etype = eval(mod + '.' + name)
-                    val = etype()
-                    val.args = args
-                except TypeError:  # string exception!
-                    etype = name
-                    val = args
-                lines = traceback.format_exception_only(etype, val)
-                for line in lines[:-1]:
-                    traceback._print(console, line, '')
-                traceback._print(console, lines[-1], '')
                 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
                     self.remote_stack_viewer()
             elif how == "ERROR":
@@ -430,36 +414,6 @@
             # we received a response to the currently active seq number:
             self.tkconsole.endexecuting()
 
-    def cleanup_traceback(self, tb, exclude, console):
-        "Remove excluded traces from beginning/end of tb; get cached lines"
-        orig_tb = tb[:]
-        while tb:
-            for rpcfile in exclude:
-                if tb[0][0].count(rpcfile):
-                    break    # found an exclude, break for: and delete tb[0]
-            else:
-                break        # no excludes, have left RPC code, break while:
-            del tb[0]
-        while tb:
-            for rpcfile in exclude:
-                if tb[-1][0].count(rpcfile):
-                    break
-            else:
-                break
-            del tb[-1]
-        if len(tb) == 0:
-            # error was in IDLE internals, don't prune!
-            tb[:] = orig_tb[:]
-            print>>sys.__stderr__, "** IDLE Internal Error: ", tb
-            print>>console, "** IDLE Internal Error **"
-        for i in range(len(tb)):
-            fn, ln, nm, line = tb[i]
-            if nm == '?':
-                nm = "-toplevel-"
-            if not line and fn.startswith("<pyshell#"):
-                line = linecache.getline(fn, ln)
-            tb[i] = fn, ln, nm, line
-
     def kill_subprocess(self):
         clt = self.rpcclt
         self.rpcclt = None
diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py
index c7644e1..b50643a 100644
--- a/Lib/idlelib/rpc.py
+++ b/Lib/idlelib/rpc.py
@@ -154,26 +154,49 @@
                 ret = remoteref(ret)
             return ("OK", ret)
         except:
-            ##traceback.print_exc(file=sys.__stderr__)
+            self.debug("localcall:EXCEPTION")
+            efile = sys.stderr
             typ, val, tb = info = sys.exc_info()
             sys.last_type, sys.last_value, sys.last_traceback = info
-            if isinstance(typ, type(Exception)):
-                # Class exception
-                mod = typ.__module__
-                name = typ.__name__
-                if issubclass(typ, Exception):
-                    args = val.args
-                else:
-                    args = (str(val),)
+            tbe = traceback.extract_tb(tb)
+            print >>efile, 'Traceback (most recent call last):'
+            exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py")
+            self.cleanup_traceback(tbe, exclude)
+            traceback.print_list(tbe, file=efile)
+            lines = traceback.format_exception_only(typ, val)
+            for line in lines:
+                print>>efile, line,
+            return ("EXCEPTION", None)
+
+    def cleanup_traceback(self, tb, exclude):
+        "Remove excluded traces from beginning/end of tb; get cached lines"
+        orig_tb = tb[:]
+        while tb:
+            for rpcfile in exclude:
+                if tb[0][0].count(rpcfile):
+                    break    # found an exclude, break for: and delete tb[0]
             else:
-                # User string exception
-                mod = None
-                name = typ
-                if val is None: val = ''
-                args = str(val)
-            tb = traceback.extract_tb(tb)
-            self.debug("localcall:EXCEPTION: ", mod, name, args, tb)
-            return ("EXCEPTION", (mod, name, args, tb))
+                break        # no excludes, have left RPC code, break while:
+            del tb[0]
+        while tb:
+            for rpcfile in exclude:
+                if tb[-1][0].count(rpcfile):
+                    break
+            else:
+                break
+            del tb[-1]
+        if len(tb) == 0:
+            # error was in RPC internals, don't prune!
+            tb[:] = orig_tb[:]
+            print>>sys.stderr, "** RPC Internal Error: ", tb
+        for i in range(len(tb)):
+            fn, ln, nm, line = tb[i]
+            if nm == '?':
+                nm = "-toplevel-"
+            if not line and fn.startswith("<pyshell#"):
+                line = self.remotecall('linecache', 'getline',
+                                       (fn, ln), {})
+            tb[i] = fn, ln, nm, line
 
     def remotecall(self, oid, methodname, args, kwargs):
         self.debug("calling asynccall via remotecall")
@@ -198,26 +221,7 @@
         if how == "OK":
             return what
         if how == "EXCEPTION":
-            self.debug("decoderesponse: EXCEPTION:", what)
-            mod, name, args, tb = what
-            self.traceback = tb
-            if mod: # not string exception
-                try:
-                    __import__(mod)
-                    module = sys.modules[mod]
-                except ImportError:
-                    pass
-                else:
-                    try:
-                        cls = getattr(module, name)
-                    except AttributeError:
-                        pass
-                    else:
-                        # instantiate a built-in exception object and raise it
-                        raise getattr(__import__(mod), name)(*args)
-                name = mod + "." + name
-            # do the best we can:
-            raise name, args
+            raise Exception, "RPC SocketIO.decoderesponse exception"
         if how == "ERROR":
             self.debug("decoderesponse: Internal ERROR:", what)
             raise RuntimeError, what