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-87868: Sort and remove duplicates in getenvironment() #102731

Merged
merged 63 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
aed39b7
gh-87868: correctly sort and remove duplicates in getenvironment()
aisk Mar 14, 2023
4de761b
Merge branch 'main' into getenvironment
AlexWaygood Mar 15, 2023
04f7d59
check null in normalize_environment
aisk Mar 17, 2023
c43abb2
Update Modules/_winapi.c
aisk Aug 20, 2023
3f6319a
Update Modules/_winapi.c
aisk Aug 20, 2023
8ef3b9c
Update Lib/test/test_subprocess.py
aisk Aug 20, 2023
46491c5
Merge remote-tracking branch 'upstream/main' into getenvironment
aisk Aug 20, 2023
ca13205
Adapt latest changes from main branch
aisk Aug 20, 2023
f727004
Fix test case
aisk Aug 20, 2023
6b9e873
update for review comments
aisk Sep 3, 2023
dd53528
add win32 related env validation tests
aisk Sep 3, 2023
37fa2f2
fix memory leak
aisk Sep 4, 2023
fcd8b88
Merge branch 'main' into getenvironment
aisk Sep 6, 2023
96754a1
Merge branch 'main' into getenvironment
aisk Sep 8, 2023
47155b5
follow PEP7
aisk Sep 8, 2023
a204740
add error check
aisk Sep 8, 2023
8ca7b8e
fix test
aisk Sep 11, 2023
b620bdb
better cleanup in error path
aisk Sep 11, 2023
65c8483
fix a mem leak
aisk Sep 13, 2023
2d2fd4d
refactor error handling
aisk Sep 13, 2023
2096209
fix one off iteration and add test
aisk Sep 19, 2023
da55b3b
fix one env test on non windows platforms
aisk Sep 19, 2023
5b17740
Merge branch 'main' into getenvironment
aisk Sep 24, 2023
bf5a555
using vector call
aisk Sep 24, 2023
66fddbe
fix test on none windows platforms
aisk Sep 24, 2023
2b82368
fix typo in test
aisk Sep 24, 2023
5c581ea
fix test on linux
aisk Sep 25, 2023
6b5d1da
revert line end modify and align codes with pep7
aisk Oct 4, 2023
bcd8910
Update Modules/_winapi.c
aisk Jan 6, 2024
3423e13
Update Modules/_winapi.c
aisk Jan 6, 2024
03a4f7e
Update Modules/_winapi.c
aisk Jan 6, 2024
186d718
Update Modules/_winapi.c
aisk Jan 6, 2024
8b074df
Update Modules/_winapi.c
aisk Jan 6, 2024
b675260
Update Modules/_winapi.c
aisk Jan 6, 2024
fb93eb1
Update Modules/_winapi.c
aisk Jan 6, 2024
f9d97eb
Update Modules/_winapi.c
aisk Jan 6, 2024
6f3b7ce
Update Modules/_winapi.c
aisk Jan 6, 2024
b9b8d6e
Update Modules/_winapi.c
aisk Jan 6, 2024
5fd7ebf
Update Modules/_winapi.c
aisk Jan 6, 2024
f87970a
Update Modules/_winapi.c
aisk Jan 6, 2024
cbdfa84
Update Lib/test/test_subprocess.py
aisk Jan 6, 2024
8748103
only keep the last duplicated environment key
aisk Jan 6, 2024
4aff3b4
split the normalize_environment function into two
aisk Jan 6, 2024
67b8731
Update Lib/test/test_subprocess.py
aisk Jan 7, 2024
d02319e
remove the error check for PyList_GET_ITEM
aisk Jan 7, 2024
d1c794b
decref the vectorcall result
aisk Jan 7, 2024
1763421
split normalze_keys into sort_keys and dedup_keys
aisk Jan 7, 2024
dbbaf1b
Update Modules/_winapi.c
aisk Jan 7, 2024
176e90d
refactor sort_environment_keys to remove the goto error stuff
aisk Jan 8, 2024
d088083
refactor the dedup_environment_keys to get rid of the goto error stuff
aisk Jan 8, 2024
471c8b8
fix a memory leak in `normalize_environment`
aisk Jan 8, 2024
a2dab93
revert the commit that moves key validation codes to the original place
aisk Jan 8, 2024
6a2bbee
fixed a memory leak
aisk Jan 8, 2024
22548cc
Update Modules/_winapi.c
aisk Jan 9, 2024
63cf753
Update Modules/_winapi.c
aisk Jan 9, 2024
91d091d
using int as return value rather than bool
aisk Jan 9, 2024
b053e2a
get rid of the goto error in normalize_environment
aisk Jan 9, 2024
5dd6215
Update Misc/NEWS.d/next/Windows/2023-03-15-23-53-45.gh-issue-87868.4C…
aisk Jan 9, 2024
be49dae
Update Modules/_winapi.c
aisk Jan 9, 2024
0aff1e9
Update Modules/_winapi.c
aisk Jan 9, 2024
72fc36d
Update Modules/_winapi.c
aisk Jan 9, 2024
725baec
Update Modules/_winapi.c
aisk Jan 9, 2024
0a308be
Update Modules/_winapi.c
aisk Jan 9, 2024
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
13 changes: 13 additions & 0 deletions Lib/test/test_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,19 @@ def test_env(self):
stdout, stderr = p.communicate()
self.assertEqual(stdout, b"orange")

@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
def test_win32_duplicate_envs(self):
aisk marked this conversation as resolved.
Show resolved Hide resolved
newenv = os.environ.copy()
newenv["fRUit"] = "cherry"
newenv["fruit"] = "lemon"
aisk marked this conversation as resolved.
Show resolved Hide resolved
newenv["FRUIT"] = "orange"
newenv["frUit"] = "banana"
with subprocess.Popen(["CMD", "/c", "SET", "fruit"],
stdout=subprocess.PIPE,
env=newenv) as p:
stdout, stderr = p.communicate()
self.assertEqual(stdout, b"fRUit=cherry\r\n")
aisk marked this conversation as resolved.
Show resolved Hide resolved

# Windows requires at least the SYSTEMROOT environment variable to start
# Python
@unittest.skipIf(sys.platform == 'win32',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Correctly sort and remove duplicate environment variables in
``_winapi.CreateProcess``.
aisk marked this conversation as resolved.
Show resolved Hide resolved
117 changes: 93 additions & 24 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -774,23 +774,111 @@ gethandle(PyObject* obj, const char* name)
return ret;
}

static PyObject* sortenvironmentkey(PyObject *module, PyObject *item) {
return _winapi_LCMapStringEx_impl(NULL, LOCALE_NAME_INVARIANT, LCMAP_UPPERCASE, item);
}

static PyMethodDef sortenvironmentkey_def = {
"sortenvironmentkey",
_PyCFunction_CAST(sortenvironmentkey),
METH_O,
""
};

static PyObject *
normalize_environment(PyObject* environment) {
PyObject *result, *keys, *keyfunc, *sort, *args, *kwargs;
aisk marked this conversation as resolved.
Show resolved Hide resolved

keys = PyMapping_Keys(environment);
if (keys == NULL) {
return NULL;
}
aisk marked this conversation as resolved.
Show resolved Hide resolved

keyfunc = PyCFunction_New(&sortenvironmentkey_def, NULL);
sort = PyObject_GetAttrString(keys, "sort");
args = PyTuple_New(0);
kwargs = PyDict_New();
aisk marked this conversation as resolved.
Show resolved Hide resolved
PyDict_SetItemString(kwargs, "key", keyfunc);
if (PyObject_Call(sort, args, kwargs) == NULL) {
aisk marked this conversation as resolved.
Show resolved Hide resolved
goto error;
}

result = PyDict_New();

for (int i=0; i<PyList_GET_SIZE(keys); i++) {
aisk marked this conversation as resolved.
Show resolved Hide resolved
PyObject *key = PyList_GET_ITEM(keys, i);
PyObject* value = PyObject_GetItem(environment, key);

if (! PyUnicode_Check(key) || ! PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"environment can only contain strings");
Py_DECREF(result);
result = NULL;
goto error;
}
if (PyUnicode_FindChar(key, '\0', 0, PyUnicode_GET_LENGTH(key), 1) != -1 ||
PyUnicode_FindChar(value, '\0', 0, PyUnicode_GET_LENGTH(value), 1) != -1)
{
PyErr_SetString(PyExc_ValueError, "embedded null character");
Py_DECREF(result);
result = NULL;
goto error;
}
/* Search from index 1 because on Windows starting '=' is allowed for
defining hidden environment variables. */
if (PyUnicode_GET_LENGTH(key) == 0 ||
aisk marked this conversation as resolved.
Show resolved Hide resolved
PyUnicode_FindChar(key, '=', 1, PyUnicode_GET_LENGTH(key), 1) != -1)
{
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
Py_DECREF(result);
result = NULL;
goto error;
}

if (i == 0) {
continue;
}

wchar_t *key_string = PyUnicode_AsWideCharString(key, NULL);
wchar_t *prev_key_string = PyUnicode_AsWideCharString(PyList_GET_ITEM(keys, i-1), NULL);
aisk marked this conversation as resolved.
Show resolved Hide resolved
aisk marked this conversation as resolved.
Show resolved Hide resolved
if (CompareStringOrdinal(prev_key_string, -1, key_string, -1, TRUE) == CSTR_EQUAL) {
aisk marked this conversation as resolved.
Show resolved Hide resolved
continue;
aisk marked this conversation as resolved.
Show resolved Hide resolved
}
PyObject_SetItem(result, key, value);
}

error:
aisk marked this conversation as resolved.
Show resolved Hide resolved
Py_DECREF(keys);
Py_DECREF(keyfunc);
Py_DECREF(sort);
Py_DECREF(args);
aisk marked this conversation as resolved.
Show resolved Hide resolved
Py_DECREF(kwargs);

return result;
}

static wchar_t *
getenvironment(PyObject* environment)
getenvironment(PyObject* env)
aisk marked this conversation as resolved.
Show resolved Hide resolved
{
Py_ssize_t i, envsize, totalsize;
wchar_t *buffer = NULL, *p, *end;
PyObject *keys, *values;
PyObject *environment = NULL, *keys = NULL, *values = NULL;

/* convert environment dictionary to windows environment string */
if (! PyMapping_Check(environment)) {
if (! PyMapping_Check(env)) {
PyErr_SetString(
PyExc_TypeError, "environment must be dictionary or None");
return NULL;
}

environment = normalize_environment(env);
if (environment == NULL) {
return NULL;
}

keys = PyMapping_Keys(environment);
if (!keys) {
return NULL;
goto error;
}
values = PyMapping_Values(environment);
if (!values) {
Expand Down Expand Up @@ -821,26 +909,6 @@ getenvironment(PyObject* environment)
PyObject* value = PyList_GET_ITEM(values, i);
Py_ssize_t size;

if (! PyUnicode_Check(key) || ! PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"environment can only contain strings");
goto error;
}
if (PyUnicode_FindChar(key, '\0', 0, PyUnicode_GET_LENGTH(key), 1) != -1 ||
PyUnicode_FindChar(value, '\0', 0, PyUnicode_GET_LENGTH(value), 1) != -1)
{
PyErr_SetString(PyExc_ValueError, "embedded null character");
goto error;
}
/* Search from index 1 because on Windows starting '=' is allowed for
defining hidden environment variables. */
if (PyUnicode_GET_LENGTH(key) == 0 ||
PyUnicode_FindChar(key, '=', 1, PyUnicode_GET_LENGTH(key), 1) != -1)
{
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
goto error;
}

size = PyUnicode_AsWideChar(key, NULL, 0);
assert(size > 1);
if (totalsize > PY_SSIZE_T_MAX - size) {
Expand Down Expand Up @@ -884,6 +952,7 @@ getenvironment(PyObject* environment)

cleanup:
error:
Py_XDECREF(environment);
Py_XDECREF(keys);
Py_XDECREF(values);
return buffer;
Expand Down
Loading