Skip to content

Commit e81f6e6

Browse files
authored
bpo-40910: Export Py_GetArgcArgv() function (GH-20721)
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().
1 parent 8f023a2 commit e81f6e6

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
@@ -984,6 +985,14 @@ customized Python always running in isolated mode using
984985
:c:func:`Py_RunMain`.
985986
986987
988+
Py_GetArgcArgv()
989+
----------------
990+
991+
.. c:function:: void Py_GetArgcArgv(int *argc, wchar_t ***argv)
992+
993+
Get the original command line arguments, before Python modified them.
994+
995+
987996
Multi-Phase Initialization Private Provisional API
988997
--------------------------------------------------
989998

Include/cpython/initconfig.h

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

416424
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);
@@ -436,5 +444,13 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config,
436444
PyWideStringList *list,
437445
Py_ssize_t length, wchar_t **items);
438446

447+
448+
/* --- Helper functions --------------------------------------- */
449+
450+
/* Get the original command line arguments, before Python modified them.
451+
452+
See also PyConfig._orig_argv. */
453+
PyAPI_FUNC(void) Py_GetArgcArgv(int *argc, wchar_t ***argv);
454+
439455
#endif /* !Py_LIMITED_API */
440456
#endif /* !Py_PYCORECONFIG_H */

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': [],
@@ -739,7 +740,12 @@ def test_init_from_config(self):
739740

740741
'pycache_prefix': 'conf_pycache_prefix',
741742
'program_name': './conf_program_name',
742-
'argv': ['-c', 'arg2', ],
743+
'argv': ['-c', 'arg2'],
744+
'_orig_argv': ['python3',
745+
'-W', 'cmdline_warnoption',
746+
'-X', 'cmdline_xoption',
747+
'-c', 'pass',
748+
'arg2'],
743749
'parse_argv': 1,
744750
'xoptions': [
745751
'config_xoption1=3',
@@ -872,6 +878,7 @@ def test_preinit_parse_argv(self):
872878
}
873879
config = {
874880
'argv': ['script.py'],
881+
'_orig_argv': ['python3', '-X', 'dev', 'script.py'],
875882
'run_filename': os.path.abspath('script.py'),
876883
'dev_mode': 1,
877884
'faulthandler': 1,
@@ -886,9 +893,14 @@ def test_preinit_dont_parse_argv(self):
886893
preconfig = {
887894
'isolated': 0,
888895
}
896+
argv = ["python3",
897+
"-E", "-I",
898+
"-X", "dev",
899+
"-X", "utf8",
900+
"script.py"]
889901
config = {
890-
'argv': ["python3", "-E", "-I",
891-
"-X", "dev", "-X", "utf8", "script.py"],
902+
'argv': argv,
903+
'_orig_argv': argv,
892904
'isolated': 0,
893905
}
894906
self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig,
@@ -967,6 +979,9 @@ def test_init_sys_add(self):
967979
'ignore:::sysadd_warnoption',
968980
'ignore:::config_warnoption',
969981
],
982+
'_orig_argv': ['python3',
983+
'-W', 'ignore:::cmdline_warnoption',
984+
'-X', 'cmdline_xoption'],
970985
}
971986
self.check_all_configs("test_init_sys_add", config, api=API_PYTHON)
972987

@@ -975,6 +990,7 @@ def test_init_run_main(self):
975990
'print(json.dumps(_testinternalcapi.get_configs()))')
976991
config = {
977992
'argv': ['-c', 'arg2'],
993+
'_orig_argv': ['python3', '-c', code, 'arg2'],
978994
'program_name': './python3',
979995
'run_command': code + '\n',
980996
'parse_argv': 1,
@@ -986,6 +1002,9 @@ def test_init_main(self):
9861002
'print(json.dumps(_testinternalcapi.get_configs()))')
9871003
config = {
9881004
'argv': ['-c', 'arg2'],
1005+
'_orig_argv': ['python3',
1006+
'-c', code,
1007+
'arg2'],
9891008
'program_name': './python3',
9901009
'run_command': code + '\n',
9911010
'parse_argv': 1,
@@ -999,6 +1018,7 @@ def test_init_parse_argv(self):
9991018
config = {
10001019
'parse_argv': 1,
10011020
'argv': ['-c', 'arg1', '-v', 'arg3'],
1021+
'_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
10021022
'program_name': './argv0',
10031023
'run_command': 'pass\n',
10041024
'use_environment': 0,
@@ -1012,6 +1032,7 @@ def test_init_dont_parse_argv(self):
10121032
config = {
10131033
'parse_argv': 0,
10141034
'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
1035+
'_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
10151036
'program_name': './argv0',
10161037
}
10171038
self.check_all_configs("test_init_dont_parse_argv", config, pre_config,
@@ -1299,10 +1320,17 @@ def test_init_warnoptions(self):
12991320
'faulthandler': 1,
13001321
'bytes_warning': 1,
13011322
'warnoptions': warnoptions,
1323+
'_orig_argv': ['python3',
1324+
'-Wignore:::cmdline1',
1325+
'-Wignore:::cmdline2'],
13021326
}
13031327
self.check_all_configs("test_init_warnoptions", config, preconfig,
13041328
api=API_PYTHON)
13051329

1330+
def test_get_argc_argv(self):
1331+
self.run_embedded_interpreter("test_get_argc_argv")
1332+
# ignore output
1333+
13061334

13071335
class AuditingTests(EmbeddingTestsMixin, unittest.TestCase):
13081336
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=python310.Py_FinalizeEx
735735
Py_GenericAlias=python310.Py_GenericAlias
736736
Py_GenericAliasType=python310.Py_GenericAliasType
737+
Py_GetArgcArgv=python310.Py_GetArgcArgv
737738
Py_GetBuildInfo=python310.Py_GetBuildInfo
738739
Py_GetCompiler=python310.Py_GetCompiler
739740
Py_GetCopyright=python310.Py_GetCopyright

Programs/_testembed.c

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

13361336
fail:
1337+
PyConfig_Clear(&config);
13371338
Py_ExitStatusException(status);
13381339
}
13391340

@@ -1592,6 +1593,46 @@ static int test_run_main(void)
15921593
}
15931594

15941595

1596+
static int test_get_argc_argv(void)
1597+
{
1598+
PyConfig config;
1599+
PyConfig_InitPythonConfig(&config);
1600+
1601+
wchar_t *argv[] = {L"python3", L"-c",
1602+
(L"import sys; "
1603+
L"print(f'Py_RunMain(): sys.argv={sys.argv}')"),
1604+
L"arg2"};
1605+
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
1606+
config_set_string(&config, &config.program_name, L"./python3");
1607+
1608+
// Calling PyConfig_Read() twice must not change Py_GetArgcArgv() result.
1609+
// The second call is done by Py_InitializeFromConfig().
1610+
PyStatus status = PyConfig_Read(&config);
1611+
if (PyStatus_Exception(status)) {
1612+
PyConfig_Clear(&config);
1613+
Py_ExitStatusException(status);
1614+
}
1615+
1616+
init_from_config_clear(&config);
1617+
1618+
int get_argc;
1619+
wchar_t **get_argv;
1620+
Py_GetArgcArgv(&get_argc, &get_argv);
1621+
printf("argc: %i\n", get_argc);
1622+
assert(get_argc == Py_ARRAY_LENGTH(argv));
1623+
for (int i=0; i < get_argc; i++) {
1624+
printf("argv[%i]: %ls\n", i, get_argv[i]);
1625+
assert(wcscmp(get_argv[i], argv[i]) == 0);
1626+
}
1627+
1628+
Py_Finalize();
1629+
1630+
printf("\n");
1631+
printf("test ok\n");
1632+
return 0;
1633+
}
1634+
1635+
15951636
/* *********************************************************
15961637
* List of test cases and the function that implements it.
15971638
*
@@ -1649,6 +1690,7 @@ static struct TestCase TestCases[] = {
16491690
{"test_init_setpythonhome", test_init_setpythonhome},
16501691
{"test_init_warnoptions", test_init_warnoptions},
16511692
{"test_run_main", test_run_main},
1693+
{"test_get_argc_argv", test_get_argc_argv},
16521694

16531695
{"test_open_code_hook", test_open_code_hook},
16541696
{"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
@@ -548,8 +548,6 @@ _Py_SetArgcArgv(Py_ssize_t argc, wchar_t * const *argv)
548548
}
549549

550550

551-
/* Make the *original* argc/argv available to other modules.
552-
This is rare, but it is needed by the secureware extension. */
553551
void
554552
Py_GetArgcArgv(int *argc, wchar_t ***argv)
555553
{
@@ -859,6 +857,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
859857
COPY_ATTR(pathconfig_warnings);
860858
COPY_ATTR(_init_main);
861859
COPY_ATTR(_isolated_interpreter);
860+
COPY_WSTRLIST(_orig_argv);
862861

863862
#undef COPY_ATTR
864863
#undef COPY_WSTR_ATTR
@@ -960,6 +959,7 @@ config_as_dict(const PyConfig *config)
960959
SET_ITEM_INT(pathconfig_warnings);
961960
SET_ITEM_INT(_init_main);
962961
SET_ITEM_INT(_isolated_interpreter);
962+
SET_ITEM_WSTRLIST(_orig_argv);
963963

964964
return dict;
965965

@@ -1856,7 +1856,7 @@ config_init_stdio(const PyConfig *config)
18561856
18571857
- set Py_xxx global configuration variables
18581858
- initialize C standard streams (stdin, stdout, stderr) */
1859-
void
1859+
PyStatus
18601860
_PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime)
18611861
{
18621862
config_set_global_vars(config);
@@ -1870,6 +1870,13 @@ _PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime)
18701870
preconfig->isolated = config->isolated;
18711871
preconfig->use_environment = config->use_environment;
18721872
preconfig->dev_mode = config->dev_mode;
1873+
1874+
if (_Py_SetArgcArgv(config->_orig_argv.length,
1875+
config->_orig_argv.items) < 0)
1876+
{
1877+
return _PyStatus_NO_MEMORY();
1878+
}
1879+
return _PyStatus_OK();
18731880
}
18741881

18751882

@@ -2493,7 +2500,6 @@ PyStatus
24932500
PyConfig_Read(PyConfig *config)
24942501
{
24952502
PyStatus status;
2496-
PyWideStringList orig_argv = _PyWideStringList_INIT;
24972503

24982504
status = _Py_PreInitializeFromConfig(config, NULL);
24992505
if (_PyStatus_EXCEPTION(status)) {
@@ -2502,8 +2508,13 @@ PyConfig_Read(PyConfig *config)
25022508

25032509
config_get_global_vars(config);
25042510

2505-
if (_PyWideStringList_Copy(&orig_argv, &config->argv) < 0) {
2506-
return _PyStatus_NO_MEMORY();
2511+
if (config->_orig_argv.length == 0
2512+
&& !(config->argv.length == 1
2513+
&& wcscmp(config->argv.items[0], L"") == 0))
2514+
{
2515+
if (_PyWideStringList_Copy(&config->_orig_argv, &config->argv) < 0) {
2516+
return _PyStatus_NO_MEMORY();
2517+
}
25072518
}
25082519

25092520
_PyPreCmdline precmdline = _PyPreCmdline_INIT;
@@ -2534,11 +2545,6 @@ PyConfig_Read(PyConfig *config)
25342545
goto done;
25352546
}
25362547

2537-
if (_Py_SetArgcArgv(orig_argv.length, orig_argv.items) < 0) {
2538-
status = _PyStatus_NO_MEMORY();
2539-
goto done;
2540-
}
2541-
25422548
/* Check config consistency */
25432549
assert(config->isolated >= 0);
25442550
assert(config->use_environment >= 0);
@@ -2591,11 +2597,11 @@ PyConfig_Read(PyConfig *config)
25912597
assert(config->check_hash_pycs_mode != NULL);
25922598
assert(config->_install_importlib >= 0);
25932599
assert(config->pathconfig_warnings >= 0);
2600+
assert(_PyWideStringList_CheckConsistency(&config->_orig_argv));
25942601

25952602
status = _PyStatus_OK();
25962603

25972604
done:
2598-
_PyWideStringList_Clear(&orig_argv);
25992605
_PyPreCmdline_Clear(&precmdline);
26002606
return status;
26012607
}

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)