error.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. """Exception and error handling.
  2. This contains the core exceptions that the implementations should raise
  3. as well as the IActiveScriptError interface code.
  4. """
  5. import sys, traceback
  6. from win32com.axscript import axscript
  7. import winerror
  8. import win32com.server.exception
  9. import win32com.server.util
  10. import pythoncom
  11. import re
  12. debugging = 0
  13. def FormatForAX(text):
  14. """Format a string suitable for an AX Host
  15. """
  16. # Replace all " with ', so it works OK in HTML (ie, ASP)
  17. return ExpandTabs(AddCR(text))
  18. def ExpandTabs(text):
  19. return re.sub('\t',' ', text)
  20. def AddCR(text):
  21. return re.sub('\n','\r\n',text)
  22. class IActiveScriptError:
  23. """An implementation of IActiveScriptError
  24. The ActiveX Scripting host calls this client whenever we report
  25. an exception to it. This interface provides the exception details
  26. for the host to report to the user.
  27. """
  28. _com_interfaces_ = [axscript.IID_IActiveScriptError]
  29. _public_methods_ = ["GetSourceLineText","GetSourcePosition","GetExceptionInfo"]
  30. def _query_interface_(self, iid):
  31. print("IActiveScriptError QI - unknown IID", iid)
  32. return 0
  33. def _SetExceptionInfo(self, exc):
  34. self.exception = exc
  35. def GetSourceLineText(self):
  36. return self.exception.linetext
  37. def GetSourcePosition(self):
  38. ctx = self.exception.sourceContext
  39. # Zero based in the debugger (but our columns are too!)
  40. return ctx, self.exception.lineno + self.exception.startLineNo-1, self.exception.colno
  41. def GetExceptionInfo(self):
  42. return self.exception
  43. class AXScriptException(win32com.server.exception.COMException):
  44. """A class used as a COM exception.
  45. Note this has attributes which conform to the standard attributes
  46. for COM exceptions, plus a few others specific to our IActiveScriptError
  47. object.
  48. """
  49. def __init__(self, site, codeBlock, exc_type, exc_value, exc_traceback):
  50. # set properties base class shares via base ctor...
  51. win32com.server.exception.COMException.__init__( self, \
  52. description = "Unknown Exception", \
  53. scode = winerror.DISP_E_EXCEPTION, \
  54. source = "Python ActiveX Scripting Engine",
  55. )
  56. # And my other values...
  57. if codeBlock is None:
  58. self.sourceContext = 0
  59. self.startLineNo = 0
  60. else:
  61. self.sourceContext = codeBlock.sourceContextCookie
  62. self.startLineNo = codeBlock.startLineNumber
  63. self.linetext = ""
  64. self.__BuildFromException(site, exc_type, exc_value, exc_traceback)
  65. def __BuildFromException(self, site, type , value, tb):
  66. if debugging:
  67. import linecache
  68. linecache.clearcache()
  69. try:
  70. if issubclass(type, SyntaxError):
  71. self._BuildFromSyntaxError(site, value, tb)
  72. else:
  73. self._BuildFromOther(site, type, value, tb)
  74. except: # Error extracting traceback info!!!
  75. traceback.print_exc()
  76. # re-raise.
  77. raise
  78. def _BuildFromSyntaxError(self, site, exc, tb):
  79. value = exc.args
  80. # All syntax errors should have a message as element 0
  81. try:
  82. msg = value[0]
  83. except:
  84. msg = "Unknown Error (%s)" % (value,)
  85. try:
  86. (filename, lineno, offset, line) = value[1]
  87. # Some of these may be None, which upsets us!
  88. if offset is None:
  89. offset = 0
  90. if line is None:
  91. line = ""
  92. except:
  93. msg = "Unknown"
  94. lineno = 0
  95. offset = 0
  96. line = "Unknown"
  97. self.description=FormatForAX(msg)
  98. self.lineno = lineno
  99. self.colno = offset - 1
  100. self.linetext = ExpandTabs(line.rstrip())
  101. def _BuildFromOther(self, site, exc_type, value, tb):
  102. self.colno = -1
  103. self.lineno = 0
  104. if debugging: # Full traceback if debugging.
  105. list=traceback.format_exception(exc_type, value, tb)
  106. self.description = ExpandTabs(''.join(list))
  107. return
  108. # Run down the traceback list, looking for the first "<Script..>"
  109. # Hide traceback above this. In addition, keep going down
  110. # looking for a "_*_" attribute, and below hide these also.
  111. hide_names = ["r_import","r_reload","r_open"] # hide from these functions down in the traceback.
  112. depth = None
  113. tb_top = tb
  114. while tb_top:
  115. filename, lineno, name, line = self.ExtractTracebackInfo(tb_top, site)
  116. if filename[:7]=="<Script":
  117. break
  118. tb_top = tb_top.tb_next
  119. format_items = []
  120. if tb_top: # found one.
  121. depth = 0
  122. tb_look = tb_top
  123. # Look down for our bottom
  124. while tb_look:
  125. filename, lineno, name, line = self.ExtractTracebackInfo(tb_look, site)
  126. if name in hide_names:
  127. break
  128. # We can report a line-number, but not a filename. Therefore,
  129. # we return the last line-number we find in one of our script
  130. # blocks.
  131. if filename.startswith("<Script"):
  132. self.lineno = lineno
  133. self.linetext = line
  134. format_items.append((filename, lineno, name, line))
  135. depth = depth + 1
  136. tb_look = tb_look.tb_next
  137. else:
  138. depth = None
  139. tb_top = tb
  140. bits = ['Traceback (most recent call last):\n']
  141. bits.extend(traceback.format_list(format_items))
  142. if exc_type==pythoncom.com_error:
  143. desc = "%s (0x%x)" % (value.strerror, value.hresult)
  144. if value.hresult==winerror.DISP_E_EXCEPTION and value.excepinfo and value.excepinfo[2]:
  145. desc = value.excepinfo[2]
  146. bits.append("COM Error: "+desc)
  147. else:
  148. bits.extend(traceback.format_exception_only(exc_type, value))
  149. # XXX - this utf8 encoding seems bogus. From well before py3k,
  150. # we had the comment:
  151. # > all items in the list are utf8 courtesy of Python magically
  152. # > converting unicode to utf8 before compilation.
  153. # but that is likely just confusion from early unicode days;
  154. # Python isn't doing it, pywin32 probably was, so 'mbcs' would
  155. # be the default encoding. We should never hit this these days
  156. # anyway, but on py3k, we *never* will, and str objects there
  157. # don't have a decode method...
  158. if sys.version_info < (3,):
  159. for i in range(len(bits)):
  160. if type(bits[i]) is str:
  161. #assert type(bits[i]) is str, type(bits[i])
  162. bits[i] = bits[i].decode('utf8')
  163. self.description = ExpandTabs(''.join(bits))
  164. # Clear tracebacks etc.
  165. tb = tb_top = tb_look = None
  166. def ExtractTracebackInfo(self, tb, site):
  167. import linecache
  168. f = tb.tb_frame
  169. lineno = tb.tb_lineno
  170. co = f.f_code
  171. filename = co.co_filename
  172. name = co.co_name
  173. line = linecache.getline(filename, lineno)
  174. if not line:
  175. try:
  176. codeBlock = site.scriptCodeBlocks[filename]
  177. except KeyError:
  178. codeBlock = None
  179. if codeBlock:
  180. # Note: 'line' will now be unicode.
  181. line = codeBlock.GetLineNo(lineno)
  182. if line:
  183. line = line.strip()
  184. else:
  185. line = None
  186. return filename, lineno, name, line
  187. def __repr__(self):
  188. return "AXScriptException Object with description:" + self.description
  189. def ProcessAXScriptException(scriptingSite, debugManager, exceptionInstance):
  190. """General function to handle any exception in AX code
  191. This function creates an instance of our IActiveScriptError interface, and
  192. gives it to the host, along with out exception class. The host will
  193. likely call back on the IActiveScriptError interface to get the source text
  194. and other information not normally in COM exceptions.
  195. """
  196. # traceback.print_exc()
  197. instance = IActiveScriptError()
  198. instance._SetExceptionInfo(exceptionInstance)
  199. gateway = win32com.server.util.wrap(instance, axscript.IID_IActiveScriptError)
  200. if debugManager:
  201. fCallOnError = debugManager.HandleRuntimeError()
  202. if not fCallOnError:
  203. return None
  204. try:
  205. result = scriptingSite.OnScriptError(gateway)
  206. except pythoncom.com_error as details:
  207. print("**OnScriptError failed:", details)
  208. print("Exception description:'%s'" % (repr(exceptionInstance.description)))
  209. print("Exception text:'%s'" % (repr(exceptionInstance.linetext)))
  210. result = winerror.S_FALSE
  211. if result==winerror.S_OK:
  212. # If the above returns NOERROR, it is assumed the error has been
  213. # correctly registered and the value SCRIPT_E_REPORTED is returned.
  214. ret = win32com.server.exception.COMException(scode=axscript.SCRIPT_E_REPORTED)
  215. return ret
  216. else:
  217. # The error is taken to be unreported and is propagated up the call stack
  218. # via the IDispatch::Invoke's EXCEPINFO parameter (hr returned is DISP_E_EXCEPTION.
  219. return exceptionInstance