@@ -1299,6 +1299,87 @@ def ref(index):
12991299 new = marshal .loads (expected )
13001300 self .check_same_structure (new , sample , True )
13011301
1302+ @support .cpython_only
1303+ def test_nan_constants_in_code (self ):
1304+ # Python has no literal for NaN, but constant folding produces one
1305+ # from arithmetic on 1e999 (inf). Such NaNs end up as a constant
1306+ # of the code object, in a tuple constant, or in a frozenset
1307+ # constant (the only kind of set the compiler creates, from a set
1308+ # display used as the right operand of "in"). Every fold creates
1309+ # a new object, so the compiler cannot produce a NaN which is an
1310+ # element of a set and is also referenced elsewhere; the ordering
1311+ # of such elements (test_nan_in_set_order) is not observable here.
1312+ # What is observable is that the code can be marshalled, and that
1313+ # the output, so the .pyc file, is the same in every process.
1314+ sources = {
1315+ 'const' : 'x = 1e999 - 1e999' ,
1316+ 'tuple' : 'x = (1e999 - 1e999, 2.0)' ,
1317+ 'repeat' : 'x = (1e999 - 1e999,) * 2' ,
1318+ 'set' : 'x in {1e999 - 1e999, 1e999 - 1e999, 2.0}' ,
1319+ 'complex' : 'x in {(1e999 - 1e999) * 1j, (1e999 - 1e999) * 2j}' ,
1320+ 'tuples' : 'x in {(1e999 - 1e999,) * 2, (1e999 - 1e999,) * 2}' ,
1321+ 'nested' : 'def f(x):\n return x in {1e999 - 1e999, 2j}' ,
1322+ }
1323+ def nan_places (code ):
1324+ # Where the NaN constants of a code object are, with the
1325+ # elements of sets in any order.
1326+ places = []
1327+ def visit (x , where ):
1328+ if isinstance (x , (float , complex )):
1329+ if x != x :
1330+ places .append ((where , repr (x )))
1331+ elif isinstance (x , (tuple , frozenset )):
1332+ for y in sorted (x , key = repr ):
1333+ visit (y , type (x ).__name__ )
1334+ elif isinstance (x , types .CodeType ):
1335+ for y in x .co_consts :
1336+ visit (y , 'consts' )
1337+ visit (code , None )
1338+ return places
1339+ expected = {
1340+ 'const' : [('consts' , 'nan' )],
1341+ 'tuple' : [('tuple' , 'nan' )],
1342+ 'repeat' : [('tuple' , 'nan' ), ('tuple' , 'nan' )],
1343+ 'set' : [('frozenset' , 'nan' ), ('frozenset' , 'nan' )],
1344+ 'complex' : [('frozenset' , '(nan+nanj)' ),
1345+ ('frozenset' , '(nan+nanj)' )],
1346+ 'tuples' : [('tuple' , 'nan' )] * 4 ,
1347+ 'nested' : [('frozenset' , 'nan' )],
1348+ }
1349+ dumps = {}
1350+ for name , source in sources .items ():
1351+ code = compile (source , '<marshal test>' , 'exec' )
1352+ self .assertEqual (nan_places (code ), expected [name ], name )
1353+ for version in range (marshal .version + 1 ):
1354+ data = marshal .dumps (code , version )
1355+ dumps [name , version ] = data
1356+ new = marshal .loads (data )
1357+ self .assertEqual (nan_places (new ), expected [name ], name )
1358+ self .assertEqual (new .co_code , code .co_code )
1359+ # Compiling again gives other NaN objects at other
1360+ # addresses, and the same bytes.
1361+ code2 = compile (source , '<marshal test>' , 'exec' )
1362+ self .assertEqual (marshal .dumps (code2 , version ), data )
1363+
1364+ # The same bytes in other processes.
1365+ script = textwrap .dedent ("""
1366+ import marshal, sys
1367+ sources = %r
1368+ for name, source in sources.items():
1369+ code = compile(source, '<marshal test>', 'exec')
1370+ for version in range(marshal.version + 1):
1371+ print(name, version, marshal.dumps(code, version).hex())
1372+ """ ) % sources
1373+ for seed in '1' , '2' , 'random' :
1374+ with self .subTest (hashseed = seed ):
1375+ rc , out , err = assert_python_ok ('-c' , script ,
1376+ PYTHONHASHSEED = seed )
1377+ for line in out .decode ().splitlines ():
1378+ name , version , data = line .split ()
1379+ self .assertEqual (bytes .fromhex (data ),
1380+ dumps [name , int (version )],
1381+ (name , version ))
1382+
13021383 def check_same_structure (self , new , sample , shared ):
13031384 # Both object graphs have the same values and, if shared is true,
13041385 # share the same objects. Elements of sets are matched in any
0 commit comments