Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-98692: Enable treating shebang lines as executables in py.exe launcher #98732

Merged
merged 7 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Lib/test/test_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,3 +617,35 @@ def test_install(self):
self.assertIn("winget.exe", cmd)
# Both command lines include the store ID
self.assertIn("9PJPW5LDXLZ5", cmd)

def test_literal_shebang_absolute(self):
with self.script(f"#! C:/some_random_app -witharg") as script:
data = self.run_py([script])
self.assertEqual(
f"C:\\some_random_app -witharg {script}",
data["stdout"].strip(),
)

def test_literal_shebang_relative(self):
with self.script(f"#! ..\\some_random_app -witharg") as script:
data = self.run_py([script])
self.assertEqual(
f"{script.parent.parent}\\some_random_app -witharg {script}",
data["stdout"].strip(),
)

def test_literal_shebang_quoted(self):
with self.script(f'#! "some random app" -witharg') as script:
data = self.run_py([script])
self.assertEqual(
f'"{script.parent}\\some random app" -witharg {script}',
data["stdout"].strip(),
)

def test_literal_shebang_quoted_escape(self):
with self.script(f'#! "some random\\\\\\" app\\\\\\\\" -witharg') as script:
data = self.run_py([script])
self.assertEqual(
f'"{script.parent}\\some random\\ app\\\\" -witharg {script}',
data["stdout"].strip(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix the :ref:`launcher` ignoring unrecognized shebang lines instead of
treating them as local paths
79 changes: 76 additions & 3 deletions PC/launcher2.c
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,77 @@ _findCommand(SearchInfo *search, const wchar_t *command, int commandLength)
}


int
_useShebangAsExecutable(SearchInfo *search, const wchar_t *shebang, int shebangLength)
{
wchar_t buffer[MAXLEN];
wchar_t script[MAXLEN];
wchar_t command[MAXLEN];

int commandLength = 0;
int inQuote = 0;
int afterSlash = 0;

if (!shebang || !shebangLength) {
return 0;
}

wchar_t *pC = command;
for (int i = 0; i < shebangLength; ++i) {
wchar_t c = shebang[i];
if ((isspace(c) || c == L'\r' || c == L'\n') && !inQuote) {
zooba marked this conversation as resolved.
Show resolved Hide resolved
commandLength = i;
break;
} else if (c == L'"') {
if (!afterSlash) {
// non-escaped quote. either way, we don't add it to the path
inQuote = !inQuote;
}
afterSlash = 0;
} else if (c == L'/' || c == L'\\') {
if (afterSlash) {
// escaped slash
*pC++ = L'\\';
afterSlash = 0;
} else {
afterSlash = 1;
}
} else {
if (afterSlash) {
*pC++ = L'\\';
afterSlash = 0;
}
*pC++ = c;
}
zooba marked this conversation as resolved.
Show resolved Hide resolved
}
*pC = L'\0';

if (!GetCurrentDirectoryW(MAXLEN, buffer) ||
wcsncpy_s(script, MAXLEN, search->scriptFile, search->scriptFileLength) ||
FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, script,
PATHCCH_ALLOW_LONG_PATHS)) ||
FAILED(PathCchRemoveFileSpec(buffer, MAXLEN)) ||
FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, command,
PATHCCH_ALLOW_LONG_PATHS))
) {
return RC_NO_MEMORY;
}
zooba marked this conversation as resolved.
Show resolved Hide resolved

int n = (int)wcsnlen(buffer, MAXLEN);
wchar_t *path = allocSearchInfoBuffer(search, n + 1);
if (!path) {
return RC_NO_MEMORY;
}
wcscpy_s(path, n + 1, buffer);
search->executablePath = path;
if (commandLength) {
search->executableArgs = &shebang[commandLength];
search->executableArgsLength = shebangLength - commandLength;
}
return 0;
}


int
checkShebang(SearchInfo *search)
{
Expand Down Expand Up @@ -963,7 +1034,6 @@ checkShebang(SearchInfo *search)
L"/usr/bin/env ",
L"/usr/bin/",
L"/usr/local/bin/",
L"",
NULL
};

Expand Down Expand Up @@ -1012,11 +1082,14 @@ checkShebang(SearchInfo *search)
debug(L"# Found shebang command but could not execute it: %.*s\n",
commandLength, command);
}
break;
// search is done by this point
return 0;
}
}

return 0;
// Unrecognised commands are joined to the script's directory and treated
// as the executable path
return _useShebangAsExecutable(search, shebang, shebangLength);
}


Expand Down