smallworld-re 1.0.3__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. smallworld/analyses/__init__.py +8 -0
  2. smallworld/analyses/analysis.py +8 -67
  3. smallworld/analyses/code_coverage.py +1 -2
  4. smallworld/analyses/colorizer.py +301 -534
  5. smallworld/analyses/colorizer_def_use.py +217 -0
  6. smallworld/analyses/colorizer_summary.py +173 -83
  7. smallworld/analyses/field_detection/field_analysis.py +7 -8
  8. smallworld/analyses/field_detection/hints.py +1 -1
  9. smallworld/analyses/field_detection/malloc.py +2 -2
  10. smallworld/analyses/trace_execution.py +160 -0
  11. smallworld/analyses/trace_execution_types.py +42 -0
  12. smallworld/analyses/unstable/angr/divergence.py +1 -2
  13. smallworld/analyses/unstable/angr/model.py +5 -6
  14. smallworld/analyses/unstable/angr_nwbt.py +3 -4
  15. smallworld/analyses/unstable/code_coverage.py +2 -3
  16. smallworld/analyses/unstable/code_reachable.py +2 -3
  17. smallworld/analyses/unstable/control_flow_tracer.py +2 -3
  18. smallworld/analyses/unstable/pointer_finder.py +2 -3
  19. smallworld/analyses/unstable/utils/tui.py +71 -0
  20. smallworld/emulators/__init__.py +3 -1
  21. smallworld/emulators/angr/angr.py +30 -9
  22. smallworld/emulators/angr/machdefs/__init__.py +2 -0
  23. smallworld/emulators/angr/machdefs/aarch64.py +1 -1
  24. smallworld/emulators/angr/machdefs/amd64.py +0 -4
  25. smallworld/emulators/angr/machdefs/arm.py +0 -2
  26. smallworld/emulators/angr/machdefs/i386.py +0 -2
  27. smallworld/emulators/angr/machdefs/loongarch.py +340 -0
  28. smallworld/emulators/angr/machdefs/machdef.py +1 -8
  29. smallworld/emulators/angr/machdefs/mips.py +0 -2
  30. smallworld/emulators/angr/machdefs/mips64.py +0 -2
  31. smallworld/emulators/angr/machdefs/ppc.py +1 -2
  32. smallworld/emulators/angr/machdefs/riscv.py +8 -10
  33. smallworld/emulators/angr/machdefs/xtensa.py +7 -4
  34. smallworld/emulators/emulator.py +22 -0
  35. smallworld/emulators/ghidra/__init__.py +37 -0
  36. smallworld/emulators/ghidra/ghidra.py +513 -0
  37. smallworld/emulators/ghidra/machdefs/__init__.py +31 -0
  38. smallworld/emulators/ghidra/machdefs/aarch64.py +289 -0
  39. smallworld/emulators/ghidra/machdefs/amd64.py +185 -0
  40. smallworld/emulators/ghidra/machdefs/arm.py +370 -0
  41. smallworld/emulators/ghidra/machdefs/i386.py +109 -0
  42. smallworld/emulators/ghidra/machdefs/loongarch.py +162 -0
  43. smallworld/emulators/ghidra/machdefs/machdef.py +81 -0
  44. smallworld/emulators/ghidra/machdefs/mips.py +163 -0
  45. smallworld/emulators/ghidra/machdefs/mips64.py +186 -0
  46. smallworld/emulators/ghidra/machdefs/ppc.py +98 -0
  47. smallworld/emulators/ghidra/machdefs/riscv.py +208 -0
  48. smallworld/emulators/ghidra/machdefs/xtensa.py +21 -0
  49. smallworld/emulators/ghidra/typing.py +28 -0
  50. smallworld/emulators/hookable.py +18 -4
  51. smallworld/emulators/panda/machdefs/__init__.py +2 -2
  52. smallworld/emulators/panda/machdefs/aarch64.py +186 -11
  53. smallworld/emulators/panda/machdefs/amd64.py +103 -11
  54. smallworld/emulators/panda/machdefs/arm.py +216 -20
  55. smallworld/emulators/panda/machdefs/i386.py +30 -7
  56. smallworld/emulators/panda/machdefs/machdef.py +9 -16
  57. smallworld/emulators/panda/machdefs/mips.py +49 -5
  58. smallworld/emulators/panda/machdefs/mips64.py +57 -5
  59. smallworld/emulators/panda/machdefs/ppc.py +38 -13
  60. smallworld/emulators/panda/panda.py +146 -44
  61. smallworld/emulators/unicorn/__init__.py +2 -0
  62. smallworld/emulators/unicorn/machdefs/aarch64.py +253 -264
  63. smallworld/emulators/unicorn/machdefs/amd64.py +254 -259
  64. smallworld/emulators/unicorn/machdefs/arm.py +200 -212
  65. smallworld/emulators/unicorn/machdefs/i386.py +84 -90
  66. smallworld/emulators/unicorn/machdefs/machdef.py +2 -23
  67. smallworld/emulators/unicorn/machdefs/mips.py +127 -135
  68. smallworld/emulators/unicorn/unicorn.py +52 -13
  69. smallworld/helpers.py +4 -19
  70. smallworld/hinting/hinting.py +22 -192
  71. smallworld/hinting/hints.py +50 -18
  72. smallworld/instructions/bsid.py +8 -8
  73. smallworld/logging.py +4 -2
  74. smallworld/platforms/__init__.py +12 -0
  75. smallworld/platforms/defs/__init__.py +36 -0
  76. smallworld/platforms/defs/aarch64.py +450 -0
  77. smallworld/platforms/defs/amd64.py +463 -0
  78. smallworld/platforms/defs/arm.py +519 -0
  79. smallworld/platforms/defs/i386.py +258 -0
  80. smallworld/platforms/defs/loongarch.py +270 -0
  81. smallworld/platforms/defs/mips.py +321 -0
  82. smallworld/platforms/defs/mips64.py +313 -0
  83. smallworld/platforms/defs/platformdef.py +97 -0
  84. smallworld/platforms/defs/powerpc.py +259 -0
  85. smallworld/platforms/defs/riscv.py +257 -0
  86. smallworld/platforms/defs/xtensa.py +96 -0
  87. smallworld/{platforms.py → platforms/platforms.py} +3 -0
  88. smallworld/state/cpus/__init__.py +2 -0
  89. smallworld/state/cpus/aarch64.py +0 -9
  90. smallworld/state/cpus/amd64.py +6 -28
  91. smallworld/state/cpus/arm.py +0 -11
  92. smallworld/state/cpus/cpu.py +0 -11
  93. smallworld/state/cpus/i386.py +0 -7
  94. smallworld/state/cpus/loongarch.py +299 -0
  95. smallworld/state/cpus/mips.py +4 -47
  96. smallworld/state/cpus/mips64.py +18 -58
  97. smallworld/state/cpus/powerpc.py +2 -9
  98. smallworld/state/cpus/riscv.py +1 -11
  99. smallworld/state/cpus/xtensa.py +0 -5
  100. smallworld/state/memory/code.py +38 -2
  101. smallworld/state/memory/elf/__init__.py +5 -1
  102. smallworld/state/memory/elf/coredump/__init__.py +3 -0
  103. smallworld/state/memory/elf/coredump/coredump.py +46 -0
  104. smallworld/state/memory/elf/coredump/prstatus/__init__.py +27 -0
  105. smallworld/state/memory/elf/coredump/prstatus/aarch64.py +46 -0
  106. smallworld/state/memory/elf/coredump/prstatus/amd64.py +40 -0
  107. smallworld/state/memory/elf/coredump/prstatus/arm.py +53 -0
  108. smallworld/state/memory/elf/coredump/prstatus/i386.py +30 -0
  109. smallworld/state/memory/elf/coredump/prstatus/mips.py +55 -0
  110. smallworld/state/memory/elf/coredump/prstatus/mips64.py +57 -0
  111. smallworld/state/memory/elf/coredump/prstatus/ppc.py +82 -0
  112. smallworld/state/memory/elf/coredump/prstatus/prstatus.py +129 -0
  113. smallworld/state/memory/elf/elf.py +211 -57
  114. smallworld/state/memory/elf/register_state.py +36 -0
  115. smallworld/state/memory/elf/rela/__init__.py +2 -0
  116. smallworld/state/memory/elf/rela/aarch64.py +3 -1
  117. smallworld/state/memory/elf/rela/amd64.py +4 -2
  118. smallworld/state/memory/elf/rela/arm.py +4 -2
  119. smallworld/state/memory/elf/rela/i386.py +4 -2
  120. smallworld/state/memory/elf/rela/loongarch.py +32 -0
  121. smallworld/state/memory/elf/rela/mips.py +39 -18
  122. smallworld/state/memory/elf/rela/ppc.py +31 -14
  123. smallworld/state/memory/elf/structs.py +3 -0
  124. smallworld/state/memory/heap.py +2 -2
  125. smallworld/state/memory/memory.py +18 -0
  126. smallworld/state/memory/pe/__init__.py +3 -0
  127. smallworld/state/memory/pe/pe.py +361 -0
  128. smallworld/state/memory/pe/structs.py +60 -0
  129. smallworld/state/memory/stack/__init__.py +2 -0
  130. smallworld/state/memory/stack/loongarch.py +26 -0
  131. smallworld/state/models/__init__.py +29 -2
  132. smallworld/state/models/aarch64/__init__.py +1 -0
  133. smallworld/state/models/aarch64/systemv/__init__.py +6 -0
  134. smallworld/state/models/aarch64/systemv/c99/__init__.py +12 -0
  135. smallworld/state/models/aarch64/systemv/c99/signal.py +16 -0
  136. smallworld/state/models/aarch64/systemv/c99/stdio.py +265 -0
  137. smallworld/state/models/aarch64/systemv/c99/stdlib.py +169 -0
  138. smallworld/state/models/aarch64/systemv/c99/string.py +139 -0
  139. smallworld/state/models/aarch64/systemv/c99/time.py +61 -0
  140. smallworld/state/models/aarch64/systemv/posix/__init__.py +6 -0
  141. smallworld/state/models/aarch64/systemv/posix/libgen.py +16 -0
  142. smallworld/state/models/aarch64/systemv/posix/signal.py +157 -0
  143. smallworld/state/models/aarch64/systemv/systemv.py +80 -0
  144. smallworld/state/models/amd64/__init__.py +1 -0
  145. smallworld/state/models/amd64/systemv/__init__.py +6 -0
  146. smallworld/state/models/amd64/systemv/c99/__init__.py +12 -0
  147. smallworld/state/models/amd64/systemv/c99/signal.py +16 -0
  148. smallworld/state/models/amd64/systemv/c99/stdio.py +265 -0
  149. smallworld/state/models/amd64/systemv/c99/stdlib.py +169 -0
  150. smallworld/state/models/amd64/systemv/c99/string.py +139 -0
  151. smallworld/state/models/amd64/systemv/c99/time.py +61 -0
  152. smallworld/state/models/amd64/systemv/posix/__init__.py +6 -0
  153. smallworld/state/models/amd64/systemv/posix/libgen.py +16 -0
  154. smallworld/state/models/amd64/systemv/posix/signal.py +157 -0
  155. smallworld/state/models/amd64/systemv/systemv.py +78 -0
  156. smallworld/state/models/armel/__init__.py +1 -0
  157. smallworld/state/models/armel/systemv/__init__.py +6 -0
  158. smallworld/state/models/armel/systemv/c99/__init__.py +12 -0
  159. smallworld/state/models/armel/systemv/c99/signal.py +16 -0
  160. smallworld/state/models/armel/systemv/c99/stdio.py +265 -0
  161. smallworld/state/models/armel/systemv/c99/stdlib.py +169 -0
  162. smallworld/state/models/armel/systemv/c99/string.py +139 -0
  163. smallworld/state/models/armel/systemv/c99/time.py +61 -0
  164. smallworld/state/models/armel/systemv/posix/__init__.py +6 -0
  165. smallworld/state/models/armel/systemv/posix/libgen.py +16 -0
  166. smallworld/state/models/armel/systemv/posix/signal.py +157 -0
  167. smallworld/state/models/armel/systemv/systemv.py +82 -0
  168. smallworld/state/models/armhf/__init__.py +1 -0
  169. smallworld/state/models/armhf/systemv/__init__.py +6 -0
  170. smallworld/state/models/armhf/systemv/c99/__init__.py +12 -0
  171. smallworld/state/models/armhf/systemv/c99/signal.py +16 -0
  172. smallworld/state/models/armhf/systemv/c99/stdio.py +265 -0
  173. smallworld/state/models/armhf/systemv/c99/stdlib.py +169 -0
  174. smallworld/state/models/armhf/systemv/c99/string.py +139 -0
  175. smallworld/state/models/armhf/systemv/c99/time.py +61 -0
  176. smallworld/state/models/armhf/systemv/posix/__init__.py +6 -0
  177. smallworld/state/models/armhf/systemv/posix/libgen.py +16 -0
  178. smallworld/state/models/armhf/systemv/posix/signal.py +157 -0
  179. smallworld/state/models/armhf/systemv/systemv.py +77 -0
  180. smallworld/state/models/c99/__init__.py +12 -0
  181. smallworld/state/models/c99/fmt_print.py +915 -0
  182. smallworld/state/models/c99/fmt_scan.py +864 -0
  183. smallworld/state/models/c99/math.py +362 -0
  184. smallworld/state/models/c99/signal.py +71 -0
  185. smallworld/state/models/c99/stdio.py +1305 -0
  186. smallworld/state/models/c99/stdlib.py +595 -0
  187. smallworld/state/models/c99/string.py +674 -0
  188. smallworld/state/models/c99/time.py +340 -0
  189. smallworld/state/models/c99/utils.py +89 -0
  190. smallworld/state/models/cstd.py +759 -0
  191. smallworld/state/models/errno.py +581 -0
  192. smallworld/state/models/filedesc.py +515 -0
  193. smallworld/state/models/i386/__init__.py +1 -0
  194. smallworld/state/models/i386/systemv/__init__.py +6 -0
  195. smallworld/state/models/i386/systemv/c99/__init__.py +12 -0
  196. smallworld/state/models/i386/systemv/c99/signal.py +16 -0
  197. smallworld/state/models/i386/systemv/c99/stdio.py +265 -0
  198. smallworld/state/models/i386/systemv/c99/stdlib.py +169 -0
  199. smallworld/state/models/i386/systemv/c99/string.py +139 -0
  200. smallworld/state/models/i386/systemv/c99/time.py +61 -0
  201. smallworld/state/models/i386/systemv/posix/__init__.py +6 -0
  202. smallworld/state/models/i386/systemv/posix/libgen.py +16 -0
  203. smallworld/state/models/i386/systemv/posix/signal.py +157 -0
  204. smallworld/state/models/i386/systemv/systemv.py +71 -0
  205. smallworld/state/models/loongarch64/__init__.py +1 -0
  206. smallworld/state/models/loongarch64/systemv/__init__.py +6 -0
  207. smallworld/state/models/loongarch64/systemv/c99/__init__.py +12 -0
  208. smallworld/state/models/loongarch64/systemv/c99/signal.py +16 -0
  209. smallworld/state/models/loongarch64/systemv/c99/stdio.py +265 -0
  210. smallworld/state/models/loongarch64/systemv/c99/stdlib.py +169 -0
  211. smallworld/state/models/loongarch64/systemv/c99/string.py +139 -0
  212. smallworld/state/models/loongarch64/systemv/c99/time.py +61 -0
  213. smallworld/state/models/loongarch64/systemv/posix/__init__.py +6 -0
  214. smallworld/state/models/loongarch64/systemv/posix/libgen.py +16 -0
  215. smallworld/state/models/loongarch64/systemv/posix/signal.py +157 -0
  216. smallworld/state/models/loongarch64/systemv/systemv.py +83 -0
  217. smallworld/state/models/mips/__init__.py +1 -0
  218. smallworld/state/models/mips/systemv/__init__.py +6 -0
  219. smallworld/state/models/mips/systemv/c99/__init__.py +12 -0
  220. smallworld/state/models/mips/systemv/c99/signal.py +16 -0
  221. smallworld/state/models/mips/systemv/c99/stdio.py +265 -0
  222. smallworld/state/models/mips/systemv/c99/stdlib.py +169 -0
  223. smallworld/state/models/mips/systemv/c99/string.py +139 -0
  224. smallworld/state/models/mips/systemv/c99/time.py +61 -0
  225. smallworld/state/models/mips/systemv/posix/__init__.py +6 -0
  226. smallworld/state/models/mips/systemv/posix/libgen.py +16 -0
  227. smallworld/state/models/mips/systemv/posix/signal.py +157 -0
  228. smallworld/state/models/mips/systemv/systemv.py +78 -0
  229. smallworld/state/models/mips64/__init__.py +1 -0
  230. smallworld/state/models/mips64/systemv/__init__.py +6 -0
  231. smallworld/state/models/mips64/systemv/c99/__init__.py +12 -0
  232. smallworld/state/models/mips64/systemv/c99/signal.py +16 -0
  233. smallworld/state/models/mips64/systemv/c99/stdio.py +265 -0
  234. smallworld/state/models/mips64/systemv/c99/stdlib.py +169 -0
  235. smallworld/state/models/mips64/systemv/c99/string.py +139 -0
  236. smallworld/state/models/mips64/systemv/c99/time.py +61 -0
  237. smallworld/state/models/mips64/systemv/posix/__init__.py +6 -0
  238. smallworld/state/models/mips64/systemv/posix/libgen.py +16 -0
  239. smallworld/state/models/mips64/systemv/posix/signal.py +157 -0
  240. smallworld/state/models/mips64/systemv/systemv.py +98 -0
  241. smallworld/state/models/mips64el/__init__.py +1 -0
  242. smallworld/state/models/mips64el/systemv/__init__.py +6 -0
  243. smallworld/state/models/mips64el/systemv/c99/__init__.py +12 -0
  244. smallworld/state/models/mips64el/systemv/c99/signal.py +16 -0
  245. smallworld/state/models/mips64el/systemv/c99/stdio.py +265 -0
  246. smallworld/state/models/mips64el/systemv/c99/stdlib.py +169 -0
  247. smallworld/state/models/mips64el/systemv/c99/string.py +139 -0
  248. smallworld/state/models/mips64el/systemv/c99/time.py +61 -0
  249. smallworld/state/models/mips64el/systemv/posix/__init__.py +6 -0
  250. smallworld/state/models/mips64el/systemv/posix/libgen.py +16 -0
  251. smallworld/state/models/mips64el/systemv/posix/signal.py +157 -0
  252. smallworld/state/models/mips64el/systemv/systemv.py +96 -0
  253. smallworld/state/models/mipsel/__init__.py +1 -0
  254. smallworld/state/models/mipsel/systemv/__init__.py +6 -0
  255. smallworld/state/models/mipsel/systemv/c99/__init__.py +12 -0
  256. smallworld/state/models/mipsel/systemv/c99/signal.py +16 -0
  257. smallworld/state/models/mipsel/systemv/c99/stdio.py +265 -0
  258. smallworld/state/models/mipsel/systemv/c99/stdlib.py +169 -0
  259. smallworld/state/models/mipsel/systemv/c99/string.py +139 -0
  260. smallworld/state/models/mipsel/systemv/c99/time.py +61 -0
  261. smallworld/state/models/mipsel/systemv/posix/__init__.py +6 -0
  262. smallworld/state/models/mipsel/systemv/posix/libgen.py +16 -0
  263. smallworld/state/models/mipsel/systemv/posix/signal.py +157 -0
  264. smallworld/state/models/mipsel/systemv/systemv.py +78 -0
  265. smallworld/state/models/model.py +27 -2
  266. smallworld/state/models/posix/__init__.py +6 -0
  267. smallworld/state/models/posix/libgen.py +123 -0
  268. smallworld/state/models/posix/signal.py +690 -0
  269. smallworld/state/models/powerpc/__init__.py +1 -0
  270. smallworld/state/models/powerpc/systemv/__init__.py +6 -0
  271. smallworld/state/models/powerpc/systemv/c99/__init__.py +12 -0
  272. smallworld/state/models/powerpc/systemv/c99/signal.py +16 -0
  273. smallworld/state/models/powerpc/systemv/c99/stdio.py +265 -0
  274. smallworld/state/models/powerpc/systemv/c99/stdlib.py +169 -0
  275. smallworld/state/models/powerpc/systemv/c99/string.py +139 -0
  276. smallworld/state/models/powerpc/systemv/c99/time.py +61 -0
  277. smallworld/state/models/powerpc/systemv/posix/__init__.py +6 -0
  278. smallworld/state/models/powerpc/systemv/posix/libgen.py +16 -0
  279. smallworld/state/models/powerpc/systemv/posix/signal.py +157 -0
  280. smallworld/state/models/powerpc/systemv/systemv.py +93 -0
  281. smallworld/state/models/riscv64/__init__.py +1 -0
  282. smallworld/state/models/riscv64/systemv/__init__.py +6 -0
  283. smallworld/state/models/riscv64/systemv/c99/__init__.py +12 -0
  284. smallworld/state/models/riscv64/systemv/c99/signal.py +16 -0
  285. smallworld/state/models/riscv64/systemv/c99/stdio.py +265 -0
  286. smallworld/state/models/riscv64/systemv/c99/stdlib.py +169 -0
  287. smallworld/state/models/riscv64/systemv/c99/string.py +139 -0
  288. smallworld/state/models/riscv64/systemv/c99/time.py +61 -0
  289. smallworld/state/models/riscv64/systemv/posix/__init__.py +6 -0
  290. smallworld/state/models/riscv64/systemv/posix/libgen.py +16 -0
  291. smallworld/state/models/riscv64/systemv/posix/signal.py +157 -0
  292. smallworld/state/models/riscv64/systemv/systemv.py +85 -0
  293. smallworld/state/state.py +65 -24
  294. smallworld/state/unstable/elf.py +16 -31
  295. smallworld/utils.py +6 -1
  296. {smallworld_re-1.0.3.dist-info → smallworld_re-2.0.0.dist-info}/METADATA +74 -42
  297. smallworld_re-2.0.0.dist-info/RECORD +374 -0
  298. {smallworld_re-1.0.3.dist-info → smallworld_re-2.0.0.dist-info}/WHEEL +1 -1
  299. smallworld/state/models/x86/__init__.py +0 -2
  300. smallworld/state/models/x86/microsoftcdecl.py +0 -35
  301. smallworld/state/models/x86/systemv.py +0 -240
  302. smallworld_re-1.0.3.dist-info/RECORD +0 -166
  303. /smallworld/state/models/{posix.py → _posix.py} +0 -0
  304. {smallworld_re-1.0.3.dist-info → smallworld_re-2.0.0.dist-info}/entry_points.txt +0 -0
  305. {smallworld_re-1.0.3.dist-info → smallworld_re-2.0.0.dist-info}/licenses/LICENSE.txt +0 -0
  306. {smallworld_re-1.0.3.dist-info → smallworld_re-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,864 @@
1
+ import abc
2
+ import re
3
+ import struct
4
+
5
+ from ....emulators import Emulator
6
+ from ....platforms import Byteorder
7
+ from ..cstd import ArgumentType, CStdModel, VariadicContext
8
+ from ..filedesc import FileDescriptor
9
+
10
+
11
+ class FormatConversionError(Exception):
12
+ pass
13
+
14
+
15
+ class InputEndedError(Exception):
16
+ pass
17
+
18
+
19
+ class Intake(metaclass=abc.ABCMeta):
20
+ """Abstract class for input stream
21
+
22
+ The format scan algorithms need to scan both files
23
+ and strings. I don't want to write two models.
24
+ """
25
+
26
+ def __init__(self):
27
+ self.cursor = 0
28
+
29
+ @abc.abstractmethod
30
+ def peek(self) -> str:
31
+ raise NotImplementedError()
32
+
33
+ @abc.abstractmethod
34
+ def push(self, data: str) -> None:
35
+ raise NotImplementedError()
36
+
37
+ @abc.abstractmethod
38
+ def pop(self) -> None:
39
+ raise NotImplementedError()
40
+
41
+
42
+ class FileIntake(Intake):
43
+ """Input stream backed by a file
44
+
45
+ That is, a file modeled by the stdio models.
46
+ """
47
+
48
+ def __init__(self, file: FileDescriptor):
49
+ super().__init__()
50
+ self.file = file
51
+
52
+ def peek(self) -> str:
53
+ data = self.file.read(1, ungetc=True)
54
+ if len(data) == 0:
55
+ raise InputEndedError()
56
+
57
+ self.file.ungetc(data[0])
58
+
59
+ if data == b"\0":
60
+ raise InputEndedError()
61
+
62
+ return data.decode("utf-8")
63
+
64
+ def push(self, data: str) -> None:
65
+ raw = data.encode("utf-8")
66
+ for char in reversed(raw):
67
+ self.file.ungetc(char)
68
+
69
+ self.cursor -= len(data)
70
+
71
+ def pop(self) -> None:
72
+ self.file.read(1, ungetc=True)
73
+ self.cursor += 1
74
+
75
+
76
+ class StringIntake(Intake):
77
+ """Input stream backed by memory.
78
+
79
+ Data is read on-demand from the emulator
80
+ """
81
+
82
+ def __init__(self, address: int, emulator: Emulator):
83
+ super().__init__()
84
+ self.address = address
85
+ self.emulator = emulator
86
+
87
+ def peek(self) -> str:
88
+ data = self.emulator.read_memory(self.address + self.cursor, 1)
89
+ if data == b"\0":
90
+ raise InputEndedError()
91
+ return data.decode("utf-8")
92
+
93
+ def push(self, data: str) -> None:
94
+ self.cursor -= len(data)
95
+
96
+ def pop(self) -> None:
97
+ self.cursor += 1
98
+
99
+
100
+ # Conversion specifiers:
101
+ #
102
+ # Group 1: Zero or one '*' flags
103
+ #
104
+ # Group 2: Zero or one maximum width specifiers
105
+ #
106
+ # Group 3: Zero or one length specifiers
107
+ #
108
+ # Group 4: Conversion ID
109
+
110
+ # Signed decimal int conversion
111
+ #
112
+ # Allowed Width Specifiers
113
+ # - 'hh': char
114
+ # - 'h': short
115
+ # - '': int
116
+ # - 'l': long
117
+ # - 'll', 'L', 'q': long long
118
+ # - 'z': ssize_t
119
+ # - 'j': intmax_t
120
+ # - t: ptrdiff_t
121
+ #
122
+ # Allowed Conversions:
123
+ # - 'd', 'i': signed decimal integer
124
+ sint_re = re.compile(r"%([*]?)([0-9]*)(hh|h|l|ll|L|q|z|j|t|)(d|i)")
125
+
126
+ # Unsigned int conversion
127
+ #
128
+ # Allowed Width Specifiers
129
+ # - 'hh': unsigned char
130
+ # - 'h': unsigned short
131
+ # - '': unsigned int
132
+ # - 'l': unsigned long
133
+ # - 'll', 'L', 'q': unsigned long long
134
+ # - 'z': size_t
135
+ # - 'j': intmax_t
136
+ # - t: ptrdiff_t
137
+ #
138
+ # Allowed Conversions:
139
+ # - 'o': unsigned octal integer
140
+ # - 'u': unsigned decimal integer
141
+ # - 'x', 'X': unsigned hexadecimal integer
142
+ uint_re = re.compile(r"%([*]?)([0-9]*)(hh|h|l|ll|L|q|z|j|t|)(o|u|x|X)")
143
+
144
+ # Floating-point conversion
145
+ #
146
+ # Allowed Width Specifiers
147
+ # - '': float
148
+ # - 'l': double
149
+ # - 'll', 'L', 'q': long double
150
+ #
151
+ # Allowed Conversions:
152
+ # - 'e', 'E', 'f', 'g', 'G', 'a', 'A': signed floating-point
153
+ float_re = re.compile(r"%([*]?)([0-9]*)(l|ll|L|q|)(e|E|f|g|a)")
154
+
155
+ # Character conversion
156
+ #
157
+ # Allowed Width Specifiers
158
+ # - '': char
159
+ # - 'l': wchar_t
160
+ #
161
+ # Allowed Conversions
162
+ # - 'c': Character
163
+ char_re = re.compile(r"%([*]?)([0-9]*)(l|)(c)")
164
+
165
+ # String conversion
166
+ #
167
+ # Allowed Width Specifiers:
168
+ # - '': char
169
+ # - 'l': wchar_t
170
+ #
171
+ # Allowed Conversions:
172
+ # - 's': String
173
+ string_re = re.compile(r"%([*]?)([0-9]*)(l|)(s)")
174
+
175
+ # Constrained String Conversion
176
+ #
177
+ # Allowed Width Specifiers:
178
+ # - '': char
179
+ # - 'l': wchar_t
180
+ #
181
+ # Allowed conversions:
182
+ # - '[.*]': Constrained string; values between '[' and ']' are allowed characters.
183
+ # - '[^.*]': Constrained string; values between '[^' and ']' are forbidden characters.
184
+
185
+ # Special case to detect '%[]]' and '%[^]]'
186
+ # These will satisfy the general regexes, but will match
187
+ # on '[]' or '[^]', which isn't correct.
188
+ brace_re = re.compile(r"%([*]?)([0-9]*)(l|)\[([\^]?\])\]")
189
+ constrained_re = re.compile(r"%([*]?)([0-9]*)(l|)\[([\^]?[\]]?[^\]]*)\]")
190
+
191
+ # Pointer Conversion
192
+ #
193
+ # Allowed Width Specifiers:
194
+ # - '': void *
195
+ #
196
+ # Allowed Conversions:
197
+ # - 'p': Pointer
198
+ pointer_re = re.compile(r"%([*]?)()([0-9]*)(p)")
199
+
200
+ # Length of current input
201
+ #
202
+ # Allowed Width Specifiers
203
+ # - 'hh': char
204
+ # - 'h': short
205
+ # - '': int
206
+ # - 'l': long
207
+ # - 'll', 'L', 'q': long long
208
+ # - 'z': ssize_t
209
+ # - 'j': intmax_t
210
+ # - 't': ptrdiff_t
211
+ #
212
+ # Allowed Conversions
213
+ # - 'n': Store number of characters consumed to this point.
214
+ length_re = re.compile(r"%([*]?)(hh|h|l|ll|z|j|t|)([0-9]*)(n)")
215
+
216
+ # Percent sign
217
+ percent_re = re.compile(r"%%")
218
+
219
+
220
+ def handle_sint(
221
+ intake: Intake, varargs: VariadicContext, m: re.Match, emulator: Emulator
222
+ ) -> bool:
223
+ flags = m.group(1)
224
+ maxwidth = -1 if m.group(2) == "" else int(m.group(2))
225
+ length = m.group(3)
226
+
227
+ strval = ""
228
+
229
+ while True:
230
+ try:
231
+ char = intake.peek()
232
+ except InputEndedError:
233
+ return False
234
+
235
+ if char.isspace():
236
+ intake.pop()
237
+ else:
238
+ break
239
+
240
+ while maxwidth == -1 or len(strval) < maxwidth:
241
+ try:
242
+ char = intake.peek()
243
+ except InputEndedError:
244
+ break
245
+
246
+ if strval == "" and char in ("+", "-"):
247
+ strval += char
248
+ intake.pop()
249
+ elif char in "0123456789":
250
+ strval += char
251
+ intake.pop()
252
+ else:
253
+ break
254
+
255
+ if strval == "":
256
+ return False
257
+
258
+ if flags == "*":
259
+ intake.push(strval)
260
+ return True
261
+
262
+ intval = int(strval)
263
+ if length in ("ll", "L", "q") and intval > (2**63):
264
+ intval &= varargs._long_long_inv_mask & ~varargs._long_long_sign_mask
265
+ elif intval > (2 ** ((varargs.platdef.address_size * 8) - 1)):
266
+ intval &= varargs._long_inv_mask & ~varargs._long_sign_mask
267
+
268
+ if intval < 0:
269
+ intval *= -1
270
+ intval = (intval ^ varargs._long_long_inv_mask) + 1
271
+
272
+ arg = varargs.get_next_argument(ArgumentType.POINTER, emulator)
273
+
274
+ assert isinstance(arg, int)
275
+
276
+ if length == "hh":
277
+ intval &= 0xFF
278
+ width = 1
279
+ elif length == "h":
280
+ intval &= 0xFFFF
281
+ width = 2
282
+ elif length == "":
283
+ intval &= varargs._int_inv_mask
284
+ width = 4
285
+ elif length in ("l", "z"):
286
+ intval &= varargs._long_inv_mask
287
+ width = 4 if ArgumentType.LONG in varargs._four_byte_types else 8
288
+ elif length in ("ll", "L", "q"):
289
+ intval &= varargs._long_long_inv_mask
290
+ width = 8
291
+ elif length == "j":
292
+ raise NotImplementedError("Type 'intmax_t' not handled")
293
+ elif length == "t":
294
+ raise NotImplementedError("Type 'ptrdiff_t' not handled")
295
+ else:
296
+ raise FormatConversionError(f"Unknown type specifier {length}")
297
+
298
+ if varargs.platform.byteorder == Byteorder.LITTLE:
299
+ byteval = intval.to_bytes(width, "little")
300
+ else:
301
+ byteval = intval.to_bytes(width, "big")
302
+
303
+ emulator.write_memory(arg, byteval)
304
+
305
+ return True
306
+
307
+
308
+ def handle_uint(
309
+ intake: Intake, varargs: VariadicContext, m: re.Match, emulator: Emulator
310
+ ) -> bool:
311
+ flags = m.group(1)
312
+ maxwidth = -1 if m.group(2) == "" else int(m.group(2))
313
+ length = m.group(3)
314
+ conversion = m.group(4).lower()
315
+
316
+ strval = ""
317
+
318
+ while True:
319
+ char = intake.peek()
320
+ if char.isspace():
321
+ intake.pop()
322
+ else:
323
+ break
324
+
325
+ while maxwidth == -1 or len(strval) < maxwidth:
326
+ try:
327
+ char = intake.peek()
328
+ except InputEndedError:
329
+ break
330
+ if conversion == "x" and char in ("x", "X") and strval == "0":
331
+ strval += char
332
+ intake.pop()
333
+ elif conversion == "x" and char in "0123456789abcdefABCDEF":
334
+ strval += char
335
+ intake.pop()
336
+ elif conversion == "u" and char in "0123456789":
337
+ strval += char
338
+ intake.pop()
339
+ elif conversion == "o" and char in "01234567":
340
+ strval += char
341
+ intake.pop()
342
+ else:
343
+ break
344
+
345
+ if strval == "":
346
+ return False
347
+
348
+ if flags == "*":
349
+ intake.push(strval)
350
+ return True
351
+
352
+ if conversion == "o":
353
+ intval = int(strval, 8)
354
+ elif conversion == "u":
355
+ intval = int(strval)
356
+ elif conversion == "x":
357
+ intval = int(strval, 16)
358
+
359
+ arg = varargs.get_next_argument(ArgumentType.POINTER, emulator)
360
+
361
+ assert isinstance(arg, int)
362
+
363
+ if length == "hh":
364
+ intval &= 0xFF
365
+ width = 1
366
+ elif length == "h":
367
+ intval &= 0xFFFF
368
+ width = 2
369
+ elif length == "":
370
+ intval &= varargs._int_inv_mask
371
+ width = 4
372
+ elif length in ("l", "z"):
373
+ intval &= varargs._long_inv_mask
374
+ width = 4 if ArgumentType.LONG in varargs._four_byte_types else 8
375
+ elif length in ("ll", "L", "q"):
376
+ intval &= varargs._long_long_inv_mask
377
+ width = 8
378
+ elif length == "j":
379
+ raise NotImplementedError("Type 'intmax_t' not handled")
380
+ elif length == "t":
381
+ raise NotImplementedError("Type 'ptrdiff_t' not handled")
382
+ else:
383
+ raise FormatConversionError(f"Unknown type specifier {length}")
384
+
385
+ if varargs.platform.byteorder == Byteorder.LITTLE:
386
+ byteval = intval.to_bytes(width, "little")
387
+ else:
388
+ byteval = intval.to_bytes(width, "big")
389
+ emulator.write_memory(arg, byteval)
390
+
391
+ return True
392
+
393
+
394
+ def handle_float(
395
+ intake: Intake, varargs: VariadicContext, m: re.Match, emulator: Emulator
396
+ ) -> bool:
397
+ flags = m.group(1)
398
+ maxwidth = -1 if m.group(2) == "" else int(m.group(2))
399
+ length = m.group(3)
400
+
401
+ found_dot = False
402
+ found_sci = False
403
+ strval = ""
404
+ fixedval = ""
405
+
406
+ # NOTE: This will reject hexadecimal floats without a specific error.
407
+ # If _anyone_ finds a legit use of hex floats since 1999, please let me know.
408
+ # I'd be most curious to see.
409
+
410
+ while True:
411
+ try:
412
+ char = intake.peek()
413
+ except InputEndedError:
414
+ return False
415
+
416
+ if char.isspace():
417
+ intake.pop()
418
+ else:
419
+ break
420
+
421
+ while maxwidth == -1 or len(strval) < maxwidth:
422
+ try:
423
+ char = intake.peek()
424
+ except InputEndedError:
425
+ break
426
+
427
+ if "infinity".startswith(strval + char):
428
+ strval += char
429
+ intake.pop()
430
+ elif "INFINITY".startswith(strval + char):
431
+ strval += char
432
+ intake.pop()
433
+ elif "nan".startswith(strval + char):
434
+ strval += char
435
+ intake.pop()
436
+ elif "NAN".startswith(strval + char):
437
+ strval += char
438
+ intake.pop()
439
+ elif "-infinity".startswith(strval + char):
440
+ strval += char
441
+ intake.pop()
442
+ elif "-INFINITY".startswith(strval + char):
443
+ strval += char
444
+ intake.pop()
445
+ elif "-nan".startswith(strval + char):
446
+ strval += char
447
+ intake.pop()
448
+ elif "-NAN".startswith(strval + char):
449
+ strval += char
450
+ intake.pop()
451
+ elif (strval == "" or strval[-1] in ("e", "E")) and char in ("+", "-"):
452
+ strval += char
453
+ intake.pop()
454
+ elif not found_dot and not found_sci and char == ".":
455
+ found_dot = True
456
+ strval += char
457
+ intake.pop()
458
+ elif not found_sci and char in ("e", "E"):
459
+ # So, scientific notation is a pain,
460
+ # because an invalid scientific number can be prefixed by a valid fixed-point number.
461
+ # Remember what came before; we'll need to restore back to it.
462
+ found_sci = True
463
+ fixedval = strval
464
+ strval += char
465
+ intake.pop()
466
+ elif char in "0123456789":
467
+ strval += char
468
+ intake.pop()
469
+ else:
470
+ break
471
+
472
+ if strval == "":
473
+ return False
474
+
475
+ if strval[-1] in ("e", "E"):
476
+ # C accepts scientific notation with no exponent.
477
+ # Python does not.
478
+ strval += "0"
479
+
480
+ try:
481
+ floatval = float(strval)
482
+ except ValueError:
483
+ if fixedval == "":
484
+ return False
485
+ try:
486
+ intake.push(strval[len(fixedval) :])
487
+ floatval = float(fixedval)
488
+ except ValueError:
489
+ intake.push(fixedval)
490
+ return False
491
+
492
+ if flags == "*":
493
+ return True
494
+
495
+ endian = ">" if varargs.platform.byteorder == Byteorder.BIG else "<"
496
+
497
+ if length == "":
498
+ byteval = struct.pack(f"{endian}f", floatval)
499
+ elif length == "l":
500
+ byteval = struct.pack(f"{endian}d", floatval)
501
+ elif length == "L":
502
+ raise NotImplementedError("long doubles not handled")
503
+ else:
504
+ raise FormatConversionError(f"Unknown type specifier {length}")
505
+
506
+ arg = varargs.get_next_argument(ArgumentType.POINTER, emulator)
507
+
508
+ assert isinstance(arg, int)
509
+
510
+ emulator.write_memory(arg, byteval)
511
+
512
+ return True
513
+
514
+
515
+ def handle_char(
516
+ intake: Intake, varargs: VariadicContext, m: re.Match, emulator: Emulator
517
+ ) -> bool:
518
+ flags = m.group(1)
519
+ maxwidth = 1 if m.group(2) == "" else int(m.group(2))
520
+ length = m.group(3)
521
+
522
+ if length == "l":
523
+ raise NotImplementedError("wchar_t not handled")
524
+ elif length != "":
525
+ raise FormatConversionError(f"Unknown type specifier {length}")
526
+
527
+ strval = ""
528
+
529
+ while len(strval) < maxwidth:
530
+ try:
531
+ char = intake.peek()
532
+ except InputEndedError:
533
+ break
534
+
535
+ strval += char
536
+ intake.pop()
537
+
538
+ if flags == "*":
539
+ return True
540
+
541
+ byteval = strval.encode("utf-8")
542
+
543
+ arg = varargs.get_next_argument(ArgumentType.POINTER, emulator)
544
+
545
+ assert isinstance(arg, int)
546
+
547
+ emulator.write_memory(arg, byteval)
548
+
549
+ return True
550
+
551
+
552
+ def handle_string(
553
+ intake: Intake, varargs: VariadicContext, m: re.Match, emulator: Emulator
554
+ ) -> bool:
555
+ flags = m.group(1)
556
+ maxwidth = -1 if m.group(2) == "" else int(m.group(2))
557
+ length = m.group(3)
558
+
559
+ if length == "l":
560
+ raise NotImplementedError("wchar_t not handled")
561
+ elif length != "":
562
+ raise FormatConversionError(f"Unknown type specifier {length}")
563
+
564
+ strval = ""
565
+
566
+ while True:
567
+ try:
568
+ char = intake.peek()
569
+ except InputEndedError:
570
+ return False
571
+
572
+ if char.isspace():
573
+ intake.pop()
574
+ else:
575
+ break
576
+
577
+ while maxwidth == -1 or len(strval) < maxwidth:
578
+ try:
579
+ char = intake.peek()
580
+ except InputEndedError:
581
+ break
582
+
583
+ if not char.isspace():
584
+ strval += char
585
+ intake.pop()
586
+ else:
587
+ break
588
+
589
+ if flags == "*":
590
+ return True
591
+
592
+ byteval = strval.encode("utf-8")
593
+ byteval += b"\0"
594
+
595
+ arg = varargs.get_next_argument(ArgumentType.POINTER, emulator)
596
+
597
+ assert isinstance(arg, int)
598
+
599
+ emulator.write_memory(arg, byteval)
600
+
601
+ return True
602
+
603
+
604
+ def handle_constrained(
605
+ intake: Intake, varargs: VariadicContext, m: re.Match, emulator: Emulator
606
+ ) -> bool:
607
+ flags = m.group(1)
608
+ maxwidth = -1 if m.group(2) == "" else int(m.group(2))
609
+ length = m.group(3)
610
+ conversion = m.group(4)
611
+
612
+ if length == "l":
613
+ raise NotImplementedError("wchar_t not handled")
614
+ elif length != "":
615
+ raise FormatConversionError(f"Unknown type specifier {length}")
616
+
617
+ if conversion[0] == "^":
618
+ invert = True
619
+ conversion = conversion[1:]
620
+
621
+ strval = ""
622
+ while maxwidth == -1 or len(strval) < maxwidth:
623
+ try:
624
+ char = intake.peek()
625
+ except InputEndedError:
626
+ break
627
+
628
+ if invert and char not in conversion:
629
+ strval += char
630
+ intake.pop()
631
+ elif not invert and char in conversion:
632
+ strval += char
633
+ intake.pop()
634
+ else:
635
+ break
636
+
637
+ if flags == "*":
638
+ return True
639
+
640
+ byteval = strval.encode("utf-8")
641
+ byteval += b"\0"
642
+
643
+ arg = varargs.get_next_argument(ArgumentType.POINTER, emulator)
644
+
645
+ assert isinstance(arg, int)
646
+
647
+ emulator.write_memory(arg, byteval)
648
+
649
+ return True
650
+
651
+
652
+ def handle_pointer(
653
+ intake: Intake, varargs: VariadicContext, m: re.Match, emulator: Emulator
654
+ ) -> bool:
655
+ flags = m.group(1)
656
+ maxwidth = -1 if m.group(2) == "" else int(m.group(2))
657
+
658
+ strval = ""
659
+
660
+ while True:
661
+ try:
662
+ char = intake.peek()
663
+ except InputEndedError:
664
+ return False
665
+
666
+ if char.isspace():
667
+ intake.pop()
668
+ else:
669
+ break
670
+
671
+ while maxwidth == -1 or len(strval) < maxwidth:
672
+ try:
673
+ char = intake.peek()
674
+ except InputEndedError:
675
+ break
676
+
677
+ if (
678
+ "(nil)".startswith(strval + char)
679
+ or "(NIL)".startswith(strval + char)
680
+ or "0x".startswith(strval + char)
681
+ ):
682
+ strval += char
683
+ intake.pop()
684
+ elif char in "0123456789abcdefABCDEF":
685
+ strval += char
686
+ intake.pop()
687
+ else:
688
+ break
689
+
690
+ if strval == "":
691
+ return False
692
+
693
+ if flags == "*":
694
+ intake.push(strval)
695
+ return True
696
+
697
+ if strval in ("(nil)", "(NIL)"):
698
+ intval = 0
699
+ else:
700
+ try:
701
+ intval = int(strval, 16)
702
+ except ValueError:
703
+ intake.push(strval)
704
+ return False
705
+
706
+ arg = varargs.get_next_argument(ArgumentType.POINTER, emulator)
707
+
708
+ assert isinstance(arg, int)
709
+
710
+ if varargs.platform.byteorder == Byteorder.BIG:
711
+ byteval = intval.to_bytes(varargs.platdef.address_size, "big")
712
+ else:
713
+ byteval = intval.to_bytes(varargs.platdef.address_size, "little")
714
+
715
+ emulator.write_memory(arg, byteval)
716
+
717
+ return True
718
+
719
+
720
+ def handle_length(
721
+ intake: Intake, varargs: VariadicContext, m: re.Match, emulator: Emulator
722
+ ) -> bool:
723
+ length = m.group(3)
724
+
725
+ intval = intake.cursor
726
+
727
+ if length == "hh":
728
+ intval &= 0xFF
729
+ width = 1
730
+ elif length == "h":
731
+ intval &= 0xFFFF
732
+ width = 2
733
+ elif length == "":
734
+ intval &= varargs._int_inv_mask
735
+ width = 4
736
+ elif length in ("l", "z"):
737
+ intval &= varargs._long_inv_mask
738
+ width = 4 if ArgumentType.LONG in varargs._four_byte_types else 8
739
+ elif length in ("ll", "L", "q"):
740
+ intval &= varargs._long_long_inv_mask
741
+ width = 8
742
+ elif length == "j":
743
+ raise NotImplementedError("Type 'intmax_t' not handled")
744
+ elif length == "t":
745
+ raise NotImplementedError("Type 'ptrdiff_t' not handled")
746
+ else:
747
+ raise FormatConversionError(f"Unknown type specifier {length}")
748
+
749
+ arg = varargs.get_next_argument(ArgumentType.POINTER, emulator)
750
+
751
+ assert isinstance(arg, int)
752
+
753
+ if varargs.platform.byteorder == Byteorder.BIG:
754
+ byteval = intval.to_bytes(width, "big")
755
+ else:
756
+ byteval = intval.to_bytes(width, "little")
757
+
758
+ emulator.write_memory(arg, byteval)
759
+
760
+ return True
761
+
762
+
763
+ def handle_percent(
764
+ intake: Intake, varargs: VariadicContext, m: re.Match, emulator: Emulator
765
+ ) -> bool:
766
+ return True
767
+
768
+
769
+ def handle_scanf_format(
770
+ model: CStdModel, intake: Intake, fmt: str, emulator: Emulator
771
+ ) -> int:
772
+ """Process a scanf format string
773
+
774
+ This is not an entirely complete model.
775
+ The following features are missing:
776
+
777
+ - Decoding for hexadecimal floats
778
+ - intmax_t integers
779
+ - ptrdiff_t integers
780
+
781
+ Arguments:
782
+ model: The parent API model
783
+ intake: The intake structure from which to fetch data
784
+ emulator: The emulator to run against
785
+
786
+ Returns:
787
+ The number of converted arguments, or -1, as per the scanf family.
788
+ """
789
+ varargs = model.get_varargs()
790
+
791
+ orig_fmt = fmt
792
+
793
+ handlers = [
794
+ (sint_re, handle_sint),
795
+ (uint_re, handle_uint),
796
+ (float_re, handle_float),
797
+ (char_re, handle_char),
798
+ (string_re, handle_string),
799
+ (brace_re, handle_constrained),
800
+ (constrained_re, handle_constrained),
801
+ (pointer_re, handle_pointer),
802
+ (length_re, handle_length),
803
+ (percent_re, handle_percent),
804
+ ]
805
+
806
+ converted = 0
807
+ done = False
808
+ while len(fmt) > 0:
809
+ if fmt[0] == "%":
810
+ # Conversion pattern. See if we can match
811
+ matched = False
812
+ for regex, handler in handlers:
813
+ m = regex.match(fmt)
814
+ if m is not None:
815
+ try:
816
+ if not handler(intake, varargs, m, emulator):
817
+ done = True
818
+ elif m.group(1) != "*" and m.group(4)[-1] != "n":
819
+ converted += 1
820
+ except FormatConversionError as e:
821
+ print(f"Bad format conversion: {e.args[0]}")
822
+ print(orig_fmt)
823
+ print(" " * (len(orig_fmt) - len(fmt)) + "^")
824
+ raise e
825
+ except Exception as e:
826
+ print(f"Exception processing conversion: {type(e)}: {e}")
827
+ print(orig_fmt)
828
+ print(" " * (len(orig_fmt) - len(fmt)) + "^")
829
+ raise e
830
+ fmt = fmt[len(m.group(0)) :]
831
+ matched = True
832
+ break
833
+
834
+ if not matched:
835
+ print("Bad format conversion: Unmatched conversion")
836
+ print(orig_fmt)
837
+ print(" " * (len(orig_fmt) - len(fmt)) + "^")
838
+ raise Exception("Bad format conversion")
839
+
840
+ if done:
841
+ break
842
+
843
+ elif fmt[0].isspace():
844
+ # Whitespace. Consume zero or more whitespace characters
845
+ while True:
846
+ char = intake.peek()
847
+ if char.isspace():
848
+ print("Space")
849
+ intake.pop()
850
+ else:
851
+ break
852
+ fmt = fmt[1:]
853
+ else:
854
+ # Normal character. Consume one character that's an exact match
855
+ char = intake.peek()
856
+ if char == fmt[0]:
857
+ intake.pop()
858
+ else:
859
+ return -1
860
+
861
+ return converted
862
+
863
+
864
+ __all__ = ["FileIntake", "StringIntake", "handle_scanf_format"]