mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	gh-99248: [Enum] fix negative number infinite loop (GH-99256)
[Enum] fix negative number infinite loop - _iter_bits_lsb() now raises a ValueError if a negative number is passed in - verify() now skips checking negative numbers for named flags
This commit is contained in:
		
							parent
							
								
									52f91c642b
								
							
						
					
					
						commit
						0b4ffb08cc
					
				
					 3 changed files with 23 additions and 2 deletions
				
			
		|  | @ -114,9 +114,12 @@ def _break_on_call_reduce(self, proto): | ||||||
|         setattr(obj, '__module__', '<unknown>') |         setattr(obj, '__module__', '<unknown>') | ||||||
| 
 | 
 | ||||||
| def _iter_bits_lsb(num): | def _iter_bits_lsb(num): | ||||||
|     # num must be an integer |     # num must be a positive integer | ||||||
|  |     original = num | ||||||
|     if isinstance(num, Enum): |     if isinstance(num, Enum): | ||||||
|         num = num.value |         num = num.value | ||||||
|  |     if num < 0: | ||||||
|  |         raise ValueError('%r is not a positive integer' % original) | ||||||
|     while num: |     while num: | ||||||
|         b = num & (~num + 1) |         b = num & (~num + 1) | ||||||
|         yield b |         yield b | ||||||
|  | @ -1839,6 +1842,9 @@ def __call__(self, enumeration): | ||||||
|                     if name in member_names: |                     if name in member_names: | ||||||
|                         # not an alias |                         # not an alias | ||||||
|                         continue |                         continue | ||||||
|  |                     if alias.value < 0: | ||||||
|  |                         # negative numbers are not checked | ||||||
|  |                         continue | ||||||
|                     values = list(_iter_bits_lsb(alias.value)) |                     values = list(_iter_bits_lsb(alias.value)) | ||||||
|                     missed = [v for v in values if v not in member_values] |                     missed = [v for v in values if v not in member_values] | ||||||
|                     if missed: |                     if missed: | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ | ||||||
| from enum import Enum, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto | from enum import Enum, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto | ||||||
| from enum import STRICT, CONFORM, EJECT, KEEP, _simple_enum, _test_simple_enum | from enum import STRICT, CONFORM, EJECT, KEEP, _simple_enum, _test_simple_enum | ||||||
| from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS, ReprEnum | from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS, ReprEnum | ||||||
| from enum import member, nonmember | from enum import member, nonmember, _iter_bits_lsb | ||||||
| from io import StringIO | from io import StringIO | ||||||
| from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL | from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL | ||||||
| from test import support | from test import support | ||||||
|  | @ -174,6 +174,10 @@ def test_is_private(self): | ||||||
|         for name in self.sunder_names + self.dunder_names + self.random_names: |         for name in self.sunder_names + self.dunder_names + self.random_names: | ||||||
|             self.assertFalse(enum._is_private('MyEnum', name), '%r is a private name?') |             self.assertFalse(enum._is_private('MyEnum', name), '%r is a private name?') | ||||||
| 
 | 
 | ||||||
|  |     def test_iter_bits_lsb(self): | ||||||
|  |         self.assertEqual(list(_iter_bits_lsb(7)), [1, 2, 4]) | ||||||
|  |         self.assertRaisesRegex(ValueError, '-8 is not a positive integer', list, _iter_bits_lsb(-8)) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # for subclassing tests | # for subclassing tests | ||||||
| 
 | 
 | ||||||
|  | @ -3960,6 +3964,16 @@ class Sillier(IntEnum): | ||||||
|             triple = 3 |             triple = 3 | ||||||
|             value = 4 |             value = 4 | ||||||
| 
 | 
 | ||||||
|  |     def test_negative_alias(self): | ||||||
|  |         @verify(NAMED_FLAGS) | ||||||
|  |         class Color(Flag): | ||||||
|  |             RED = 1 | ||||||
|  |             GREEN = 2 | ||||||
|  |             BLUE = 4 | ||||||
|  |             WHITE = -1 | ||||||
|  |         # no error means success | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class TestInternals(unittest.TestCase): | class TestInternals(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|     sunder_names = '_bad_', '_good_', '_what_ho_' |     sunder_names = '_bad_', '_good_', '_what_ho_' | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | fix negative numbers failing in verify() | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ethan Furman
						Ethan Furman