Skip to content

Commit dedaac0

Browse files
authored
bpo-40910: Export Py_GetArgcArgv() function (GH-20721) (GH-20723)
Export explicitly the Py_GetArgcArgv() function to the C API and document the function. Previously, it was exported implicitly which no longer works since Python is built with -fvisibility=hidden. * Add PyConfig._orig_argv member. * Py_InitializeFromConfig() no longer calls _PyConfig_Write() twice. * PyConfig_Read() no longer initializes Py_GetArgcArgv(): it is now _PyConfig_Write() responsibility. * _PyConfig_Write() result type becomes PyStatus instead of void. * Write an unit test on Py_GetArgcArgv(). (cherry picked from commit e81f6e6)
1 parent 1220a47 commit dedaac0

File tree

10 files changed

+131
-22
lines changed

10 files changed

+131
-22
lines changed

Doc/c-api/init_config.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Functions:
4343
* :c:func:`Py_PreInitializeFromArgs`
4444
* :c:func:`Py_PreInitializeFromBytesArgs`
4545
* :c:func:`Py_RunMain`
46+
* :c:func:`Py_GetArgcArgv`
4647

4748
The preconfiguration (``PyPreConfig`` type) is stored in
4849
``_PyRuntime.preconfig`` and the configuration (``PyConfig`` type) is stored in
@@ -975,6 +976,14 @@ customized Python always running in isolated mode using
975976
:c:func:`Py_RunMain`.
976977
977978
979+
Py_GetArgcArgv()
980+
----------------
981+
982+
.. c:function:: void Py_GetArgcArgv(int *argc, wchar_t ***argv)
983+
984+
Get the original command line arguments, before Python modified them.
985+
986+
978987
Multi-Phase Initialization Private Provisional API
979988
--------------------------------------------------
980989

Include/cpython/initconfig.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,14 @@ typedef struct {
413413
/* If non-zero, disallow threads, subprocesses, and fork.
414414
Default: 0. */
415415
int _isolated_interpreter;
416+
417+
/* Original command line arguments. If _orig_argv is empty and _argv is
418+
not equal to [''], PyConfig_Read() copies the configuration 'argv' list
419+
into '_orig_argv' list before modifying 'argv' list (if parse_argv
420+
is non-zero).
421+
422+
_PyConfig_Write() initializes Py_GetArgcArgv() to this list. */
423+
PyWideStringList _orig_argv;
416424
} PyConfig;
417425

418426
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);
@@ -438,6 +446,14 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config,
438446
PyWideStringList *list,
439447
Py_ssize_t length, wchar_t **items);
440448

449+
450+
/* --- Helper functions --------------------------------------- */
451+
452+
/* Get the original command line arguments, before Python modified them.
453+
454+
See also PyConfig._orig_argv. */
455+
PyAPI_FUNC(void) Py_GetArgcArgv(int *argc, wchar_t ***argv);
456+
441457
#ifdef __cplusplus
442458
}
443459
#endif

Include/internal/pycore_initconfig.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ extern PyStatus _PyConfig_Copy(
150150
PyConfig *config,
151151
const PyConfig *config2);
152152
extern PyStatus _PyConfig_InitPathConfig(PyConfig *config);
153-
extern void _PyConfig_Write(const PyConfig *config,
153+
extern PyStatus _PyConfig_Write(const PyConfig *config,
154154
struct pyruntimestate *runtime);
155155
extern PyStatus _PyConfig_SetPyArgv(
156156
PyConfig *config,

Lib/test/test_embed.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
366366
'program_name': GET_DEFAULT_CONFIG,
367367
'parse_argv': 0,
368368
'argv': [""],
369+
'_orig_argv': [],
369370

370371
'xoptions': [],
371372
'warnoptions': [],
@@ -737,7 +738,12 @@ def test_init_from_config(self):
737738

738739
'pycache_prefix': 'conf_pycache_prefix',
739740
'program_name': './conf_program_name',
740-
'argv': ['-c', 'arg2', ],
741+
'argv': ['-c', 'arg2'],
742+
'_orig_argv': ['python3',
743+
'-W', 'cmdline_warnoption',
744+
'-X', 'cmdline_xoption',
745+
'-c', 'pass',
746+
'arg2'],
741747
'parse_argv': 1,
742748
'xoptions': [
743749
'config_xoption1=3',
@@ -864,6 +870,7 @@ def test_preinit_parse_argv(self):
864870
}
865871
config = {
866872
'argv': ['script.py'],
873+
'_orig_argv': ['python3', '-X', 'dev', 'script.py'],
867874
'run_filename': os.path.abspath('script.py'),
868875
'dev_mode': 1,
869876
'faulthandler': 1,
@@ -878,9 +885,14 @@ def test_preinit_dont_parse_argv(self):
878885
preconfig = {
879886
'isolated': 0,
880887
}
888+
argv = ["python3",
889+
"-E", "-I",
890+
"-X", "dev",
891+
"-X", "utf8",
892+
"script.py"]
881893
config = {
882-
'argv': ["python3", "-E", "-I",
883-
"-X", "dev", "-X", "utf8", "script.py"],
894+
'argv': argv,
895+
'_orig_argv': argv,
884896
'isolated': 0,
885897
}
886898
self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig,
@@ -959,6 +971,9 @@ def test_init_sys_add(self):
959971
'ignore:::sysadd_warnoption',
960972
'ignore:::config_warnoption',
961973
],
974+
'_orig_argv': ['python3',
975+
'-W', 'ignore:::cmdline_warnoption',
976+
'-X', 'cmdline_xoption'],
962977
}
963978
self.check_all_configs("test_init_sys_add", config, api=API_PYTHON)
964979

@@ -967,6 +982,7 @@ def test_init_run_main(self):
967982
'print(json.dumps(_testinternalcapi.get_configs()))')
968983
config = {
969984
'argv': ['-c', 'arg2'],
985+
'_orig_argv': ['python3', '-c', code, 'arg2'],
970986
'program_name': './python3',
971987
'run_command': code + '\n',
972988
'parse_argv': 1,
@@ -978,6 +994,9 @@ def test_init_main(self):
978994
'print(json.dumps(_testinternalcapi.get_configs()))')
979995
config = {
980996
'argv': ['-c', 'arg2'],
997+
'_orig_argv': ['python3',
998+
'-c', code,
999+
'arg2'],
9811000
'program_name': './python3',
9821001
'run_command': code + '\n',
9831002
'parse_argv': 1,
@@ -991,6 +1010,7 @@ def test_init_parse_argv(self):
9911010
config = {
9921011
'parse_argv': 1,
9931012
'argv': ['-c', 'arg1', '-v', 'arg3'],
1013+
'_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
9941014
'program_name': './argv0',
9951015
'run_command': 'pass\n',
9961016
'use_environment': 0,
@@ -1004,6 +1024,7 @@ def test_init_dont_parse_argv(self):
10041024
config = {
10051025
'parse_argv': 0,
10061026
'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
1027+
'_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
10071028
'program_name': './argv0',
10081029
}
10091030
self.check_all_configs("test_init_dont_parse_argv", config, pre_config,
@@ -1291,10 +1312,17 @@ def test_init_warnoptions(self):
12911312
'faulthandler': 1,
12921313
'bytes_warning': 1,
12931314
'warnoptions': warnoptions,
1315+
'_orig_argv': ['python3',
1316+
'-Wignore:::cmdline1',
1317+
'-Wignore:::cmdline2'],
12941318
}
12951319
self.check_all_configs("test_init_warnoptions", config, preconfig,
12961320
api=API_PYTHON)
12971321

1322+
def test_get_argc_argv(self):
1323+
self.run_embedded_interpreter("test_get_argc_argv")
1324+
# ignore output
1325+
12981326

12991327
class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
13001328
def test_open_code_hook(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Export explicitly the :c:func:`Py_GetArgcArgv` function to the C API and
2+
document the function. Previously, it was exported implicitly which no
3+
longer works since Python is built with ``-fvisibility=hidden``.

PC/python3.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ EXPORTS
734734
Py_FinalizeEx=python39.Py_FinalizeEx
735735
Py_GenericAlias=python39.Py_GenericAlias
736736
Py_GenericAliasType=python39.Py_GenericAliasType
737+
Py_GetArgcArgv=python39.Py_GetArgcArgv
737738
Py_GetBuildInfo=python39.Py_GetBuildInfo
738739
Py_GetCompiler=python39.Py_GetCompiler
739740
Py_GetCopyright=python39.Py_GetCopyright

Programs/_testembed.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,7 @@ static int test_init_read_set(void)
13261326
return 0;
13271327

13281328
fail:
1329+
PyConfig_Clear(&config);
13291330
Py_ExitStatusException(status);
13301331
}
13311332

@@ -1584,6 +1585,46 @@ static int test_run_main(void)
15841585
}
15851586

15861587

1588+
static int test_get_argc_argv(void)
1589+
{
1590+
PyConfig config;
1591+
PyConfig_InitPythonConfig(&config);
1592+
1593+
wchar_t *argv[] = {L"python3", L"-c",
1594+
(L"import sys; "
1595+
L"print(f'Py_RunMain(): sys.argv={sys.argv}')"),
1596+
L"arg2"};
1597+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
1598+
config_set_string(&config, &config.program_name, L"./python3");
1599+
1600+
// Calling PyConfig_Read() twice must not change Py_GetArgcArgv() result.
1601+
// The second call is done by Py_InitializeFromConfig().
1602+
PyStatus status = PyConfig_Read(&config);
1603+
if (PyStatus_Exception(status)) {
1604+
PyConfig_Clear(&config);
1605+
Py_ExitStatusException(status);
1606+
}
1607+
1608+
init_from_config_clear(&config);
1609+
1610+
int get_argc;
1611+
wchar_t **get_argv;
1612+
Py_GetArgcArgv(&get_argc, &get_argv);
1613+
printf("argc: %i\n", get_argc);
1614+
assert(get_argc == Py_ARRAY_LENGTH(argv));
1615+
for (int i=0; i < get_argc; i++) {
1616+
printf("argv[%i]: %ls\n", i, get_argv[i]);
1617+
assert(wcscmp(get_argv[i], argv[i]) == 0);
1618+
}
1619+
1620+
Py_Finalize();
1621+
1622+
printf("\n");
1623+
printf("test ok\n");
1624+
return 0;
1625+
}
1626+
1627+
15871628
/* *********************************************************
15881629
* List of test cases and the function that implements it.
15891630
*
@@ -1641,6 +1682,7 @@ static struct TestCase TestCases[] = {
16411682
{"test_init_setpythonhome", test_init_setpythonhome},
16421683
{"test_init_warnoptions", test_init_warnoptions},
16431684
{"test_run_main", test_run_main},
1685+
{"test_get_argc_argv", test_get_argc_argv},
16441686

16451687
{"test_open_code_hook", test_open_code_hook},
16461688
{"test_audit", test_audit},

Python/bootstrap_hash.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ _Py_HashRandomization_Init(const PyConfig *config)
580580
res = pyurandom(secret, secret_size, 0, 0);
581581
if (res < 0) {
582582
return _PyStatus_ERR("failed to get random numbers "
583-
"to initialize Python");
583+
"to initialize Python");
584584
}
585585
}
586586
return _PyStatus_OK();

Python/initconfig.c

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,6 @@ _Py_SetArgcArgv(Py_ssize_t argc, wchar_t * const *argv)
543543
}
544544

545545

546-
/* Make the *original* argc/argv available to other modules.
547-
This is rare, but it is needed by the secureware extension. */
548546
void
549547
Py_GetArgcArgv(int *argc, wchar_t ***argv)
550548
{
@@ -852,6 +850,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
852850
COPY_ATTR(pathconfig_warnings);
853851
COPY_ATTR(_init_main);
854852
COPY_ATTR(_isolated_interpreter);
853+
COPY_WSTRLIST(_orig_argv);
855854

856855
#undef COPY_ATTR
857856
#undef COPY_WSTR_ATTR
@@ -952,6 +951,7 @@ config_as_dict(const PyConfig *config)
952951
SET_ITEM_INT(pathconfig_warnings);
953952
SET_ITEM_INT(_init_main);
954953
SET_ITEM_INT(_isolated_interpreter);
954+
SET_ITEM_WSTRLIST(_orig_argv);
955955

956956
return dict;
957957

@@ -1832,7 +1832,7 @@ config_init_stdio(const PyConfig *config)
18321832
18331833
- set Py_xxx global configuration variables
18341834
- initialize C standard streams (stdin, stdout, stderr) */
1835-
void
1835+
PyStatus
18361836
_PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime)
18371837
{
18381838
config_set_global_vars(config);
@@ -1846,6 +1846,13 @@ _PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime)
18461846
preconfig->isolated = config->isolated;
18471847
preconfig->use_environment = config->use_environment;
18481848
preconfig->dev_mode = config->dev_mode;
1849+
1850+
if (_Py_SetArgcArgv(config->_orig_argv.length,
1851+
config->_orig_argv.items) < 0)
1852+
{
1853+
return _PyStatus_NO_MEMORY();
1854+
}
1855+
return _PyStatus_OK();
18491856
}
18501857

18511858

@@ -2469,7 +2476,6 @@ PyStatus
24692476
PyConfig_Read(PyConfig *config)
24702477
{
24712478
PyStatus status;
2472-
PyWideStringList orig_argv = _PyWideStringList_INIT;
24732479

24742480
status = _Py_PreInitializeFromConfig(config, NULL);
24752481
if (_PyStatus_EXCEPTION(status)) {
@@ -2478,8 +2484,13 @@ PyConfig_Read(PyConfig *config)
24782484

24792485
config_get_global_vars(config);
24802486

2481-
if (_PyWideStringList_Copy(&orig_argv, &config->argv) < 0) {
2482-
return _PyStatus_NO_MEMORY();
2487+
if (config->_orig_argv.length == 0
2488+
&& !(config->argv.length == 1
2489+
&& wcscmp(config->argv.items[0], L"") == 0))
2490+
{
2491+
if (_PyWideStringList_Copy(&config->_orig_argv, &config->argv) < 0) {
2492+
return _PyStatus_NO_MEMORY();
2493+
}
24832494
}
24842495

24852496
_PyPreCmdline precmdline = _PyPreCmdline_INIT;
@@ -2510,11 +2521,6 @@ PyConfig_Read(PyConfig *config)
25102521
goto done;
25112522
}
25122523

2513-
if (_Py_SetArgcArgv(orig_argv.length, orig_argv.items) < 0) {
2514-
status = _PyStatus_NO_MEMORY();
2515-
goto done;
2516-
}
2517-
25182524
/* Check config consistency */
25192525
assert(config->isolated >= 0);
25202526
assert(config->use_environment >= 0);
@@ -2566,11 +2572,11 @@ PyConfig_Read(PyConfig *config)
25662572
assert(config->check_hash_pycs_mode != NULL);
25672573
assert(config->_install_importlib >= 0);
25682574
assert(config->pathconfig_warnings >= 0);
2575+
assert(_PyWideStringList_CheckConsistency(&config->_orig_argv));
25692576

25702577
status = _PyStatus_OK();
25712578

25722579
done:
2573-
_PyWideStringList_Clear(&orig_argv);
25742580
_PyPreCmdline_Clear(&precmdline);
25752581
return status;
25762582
}

Python/pylifecycle.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,10 @@ pyinit_core_reconfigure(_PyRuntimeState *runtime,
460460
return _PyStatus_ERR("can't make main interpreter");
461461
}
462462

463-
_PyConfig_Write(config, runtime);
463+
status = _PyConfig_Write(config, runtime);
464+
if (_PyStatus_EXCEPTION(status)) {
465+
return status;
466+
}
464467

465468
status = _PyInterpreterState_SetConfig(interp, config);
466469
if (_PyStatus_EXCEPTION(status)) {
@@ -486,7 +489,10 @@ pycore_init_runtime(_PyRuntimeState *runtime,
486489
return _PyStatus_ERR("main interpreter already initialized");
487490
}
488491

489-
_PyConfig_Write(config, runtime);
492+
PyStatus status = _PyConfig_Write(config, runtime);
493+
if (_PyStatus_EXCEPTION(status)) {
494+
return status;
495+
}
490496

491497
/* Py_Finalize leaves _Py_Finalizing set in order to help daemon
492498
* threads behave a little more gracefully at interpreter shutdown.
@@ -499,7 +505,7 @@ pycore_init_runtime(_PyRuntimeState *runtime,
499505
*/
500506
_PyRuntimeState_SetFinalizing(runtime, NULL);
501507

502-
PyStatus status = _Py_HashRandomization_Init(config);
508+
status = _Py_HashRandomization_Init(config);
503509
if (_PyStatus_EXCEPTION(status)) {
504510
return status;
505511
}
@@ -746,8 +752,6 @@ pyinit_config(_PyRuntimeState *runtime,
746752
PyThreadState **tstate_p,
747753
const PyConfig *config)
748754
{
749-
_PyConfig_Write(config, runtime);
750-
751755
PyStatus status = pycore_init_runtime(runtime, config);
752756
if (_PyStatus_EXCEPTION(status)) {
753757
return status;

0 commit comments

Comments
 (0)