Register module paths as site directories; dedupe sys.path (1.8.0) - #17
Open
ndonkoHenri wants to merge 4 commits into
Open
Register module paths as site directories; dedupe sys.path (1.8.0)#17ndonkoHenri wants to merge 4 commits into
sys.path (1.8.0)#17ndonkoHenri wants to merge 4 commits into
Conversation
CPython only processes `.pth` files for site directories, never for PYTHONPATH entries. Every serious_python plugin exposes the app's bundled site-packages through PYTHONPATH / module_paths only, so packages relying on a `.pth` to extend sys.path or run bootstrap code were broken in packaged apps. pywin32 is the canonical case: `pywin32.pth` adds `win32`, `win32\lib`, `pythonwin` and imports `pywin32_bootstrap` (which registers `pywin32_system32` as a DLL directory); without it `import win32com` fails with "No module named 'pywintypes'" (flet-dev/flet#5071). sp_apply_module_paths now emits a fixed epilogue that: * drops existing sys.path occurrences of each module path (the plugins pass the same list as PYTHONPATH and module_paths, so every entry was listed twice) and re-inserts the list at the front, preserving order; * calls site.addsitedir() on every module path that is a directory, so `.pth` path lines are appended exactly as in a regular installation; zips / missing dirs are skipped; * removes its temporaries from __main__'s globals.
The buffer estimate for the generated `_sp_paths` literal budgeted ~3 output bytes per input char, but the apostrophe escape (close the raw string, concatenate "'", reopen) emits 8. An apostrophe-dense path could exhaust the buffer; the copy guard then both reserved too little for its own write (4 bytes vs the escape's 8) and truncated the path silently, after which `total - (p - code)` underflowed as size_t and gave the epilogue snprintf an effectively unbounded size. Budget 8 bytes per input char — the true worst case, so the guard is now unreachable and kept only as a defensive bound with a margin covering its largest write (8-byte escape + "']" + NUL). Also correct the stale quoting comment: backslashes are preserved by the raw-string fragments, only apostrophes are rewritten.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes flet-dev/flet#5071
Problem
CPython processes
.pthfiles only for site directories (site.pyoverPYTHONHOME'sLib/site-packages) — never forPYTHONPATHentries. Everyserious_python_*plugin exposes the app's bundledsite-packagesthroughPYTHONPATH/module_pathsonly, so any package that relies on a.pthfile to extendsys.pathor run bootstrap code silently broke in packaged apps.pywin32 is the canonical case. Its layout only works through
pywin32.pth, which addswin32,win32\libandpythonwintosys.pathand importspywin32_bootstrap— which in turn registerspywin32_system32as a DLL directory so the.pydextensions can resolvepywintypes3xx.dll. With the.pthunprocessed,import win32comin aflet build windowsapp fails withModuleNotFoundError: No module named 'pywintypes'.Fix
sp_apply_module_pathsnow emits the module-path list into a temporary and runs a fixed epilogue (SP_MODULE_PATHS_EPILOGUE) that:sys.path: The plugins pass the same list both asPYTHONPATH(consumed byPy_Initialize) and asmodule_paths(inserted post-init), so every entry appeared twice. Existing occurrences are removed — compared case-/separator-insensitively viaos.path.normcase(os.path.abspath(...))— and the list is re-inserted at the front, preserving the caller's precedence order.site.addsitedir()— the same functionsite.pyuses for real site dirs:.pthpath lines are appended after the existing entries exactly as in a regular installation;importlines execute. Non-directories (Android's stdlib/site-packages zips, an absent__pypackages__) are skipped.__main__'s globals, which this snippet shares with the user's program.A malformed
.pthis not fatal:siteprints its usual "Error processing line …" warning and continues, matching stock CPython behavior.Also: buffer sizing for the apostrophe escape (
24f4d76)While touching
sp_apply_module_paths, a pre-existing (since 1.5.0) flaw in its buffer math was corrected. The estimate for the generated_sp_pathsliteral budgeted ~3 output bytes per input char, but the apostrophe escape (close the raw string, concatenate"'", reopen) emits 8. An apostrophe-dense path could exhaust the buffer, and the copy guard both reserved too little for its own write (4 bytes vs the escape's 8) and truncated the path silently — after whichtotal - (p - code)underflowed assize_t, handing the epiloguesnprintfan effectively unbounded size (heap overflow). Only reachable with pathological paths (a single apostrophe, e.g.C:\Users\O'Brien\…, stays well within budget), but it was wrong.The buffer is now sized for the true worst case (8 bytes per input char), which makes the guard unreachable; it is kept as a defensive bound with a margin covering its largest write. Validated with a byte-accurate replica of the generator (including
snprintf's would-be-length advancement) driven with adversarial inputs — 500-apostrophe paths, dense mixes — plus round-tripcompile()/execof every generated program: no overflow, guard never reached, paths reproduce exactly.Verification
Reproduced and verified on a Windows 11 VM against Flet v0.86.5:
**Test Code**
import win32com.client→No module named 'pywintypes'; everysys.pathentry listed twice.dart_bridge.dllin the built app):win32com.client.Dispatch("WScript.Shell")succeeds;sys.pathentries appear once, withwin32,win32\lib,pythonwinappended at the end.