From 3432e7d4dd619bad764841b904dadbb2801e03cc Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Tue, 25 Jun 2019 14:21:57 -0400 Subject: [PATCH 1/5] crash.subsystem.filesystem: add inode bits and ls-style formatting This commit adds the constants for inode mode bits as well as routines to format them into ls-style permissions. Signed-off-by: Jeff Mahoney --- crash/subsystem/filesystem/__init__.py | 138 +++++++++++++ tests/test_subsystem_filesystem.py | 257 +++++++++++++++++++++++++ 2 files changed, 395 insertions(+) create mode 100644 tests/test_subsystem_filesystem.py diff --git a/crash/subsystem/filesystem/__init__.py b/crash/subsystem/filesystem/__init__.py index 98fc056f0fa..6b2e7e5e929 100644 --- a/crash/subsystem/filesystem/__init__.py +++ b/crash/subsystem/filesystem/__init__.py @@ -77,6 +77,144 @@ MS_NOUSER : "MS_NOUSER", } +S_IFMT = 0o170000 +S_IFSOCK = 0o140000 +S_IFLNK = 0o120000 +S_IFREG = 0o100000 +S_IFBLK = 0o060000 +S_IFDIR = 0o040000 +S_IFCHR = 0o020000 +S_IFIFO = 0o010000 + +S_ISUID = 0o0004000 +S_ISGID = 0o0002000 +S_ISVTX = 0o0001000 + +S_IRWXU = 0o00700 +S_IRUSR = 0o00400 +S_IWUSR = 0o00200 +S_IXUSR = 0o00100 + +S_IRWXG = 0o00070 +S_IRGRP = 0o00040 +S_IWGRP = 0o00020 +S_IXGRP = 0o00010 + +S_IRWXO = 0o00007 +S_IROTH = 0o00004 +S_IWOTH = 0o00002 +S_IXOTH = 0o00001 + +INODE_MODE_BITS = { + S_IFSOCK : 'S_IFSOCK', + S_IFLNK : 'S_IFLNK', + S_IFREG : 'S_IFREG', + S_IFBLK : 'S_IFBLK', + S_IFDIR : 'S_IFDIR', + S_IFCHR : 'S_IFCHR', + S_IFIFO : 'S_IFIFO', + S_ISUID : 'S_ISUID', + S_ISGID : 'S_ISGID', + S_ISVTX : 'S_ISVTX', + S_IRWXU : 'S_IRWXU', + S_IRUSR : 'S_IRUSR', + S_IWUSR : 'S_IWUSR', + S_IXUSR : 'S_IXUSR', + S_IRWXG : 'S_IRWXG', + S_IRGRP : 'S_IRGRP', + S_IWGRP : 'S_IWGRP', + S_IXGRP : 'S_IXGRP', + S_IRWXO : 'S_IRWXO', + S_IROTH : 'S_IROTH', + S_IWOTH : 'S_IWOTH', + S_IXOTH : 'S_IXOTH', +} + +_inode_fmt_bits = { + S_IFSOCK : 's', + S_IFLNK : 'l', + S_IFREG : '-', + S_IFBLK : 'b', + S_IFDIR : 'd', + S_IFCHR : 'c', + S_IFIFO : 'p', +} + +_inode_rwx_bits = { + S_IRUSR : 'r', + S_IWUSR : 'w', + S_IXUSR : 'x', + S_IRGRP : 'r', + S_IWGRP : 'w', + S_IXGRP : 'x', + S_IROTH : 'r', + S_IWOTH : 'w', + S_IXOTH : 'x', +} + +def ls_style_mode_perms(i_mode: gdb.Value) -> str: + mode = int(i_mode) + + fmt = '?' + for bit in sorted(_inode_fmt_bits.keys()): + if (bit & i_mode) == bit: + fmt = _inode_fmt_bits[bit] + + perms = [fmt] + + for bit in sorted(_inode_rwx_bits.keys(), reverse=True): + if (bit & i_mode) == bit: + perms.append(_inode_rwx_bits[bit]) + else: + perms.append('-') + + if mode & S_ISUID: + if mode & S_IXUSR: + perms[3] = 's' + else: + perms[3] = 'S' + + if mode & S_ISGID: + if mode & S_IXGRP: + perms[6] = 's' + else: + perms[6] = 'S' + + if mode & S_ISVTX: + if mode & S_IXOTH: + perms[9] = 't' + else: + perms[9] = 'T' + + return "".join(perms) + +def ls_style_inode_perms(inode: gdb.Value) -> str: + return ls_style_mode_perms(inode['i_mode']) + +def _S_ISMODE(i_mode: int, mode: int) -> bool: + return (i_mode & S_IFMT) == mode + +def S_ISLNK(i_mode: int) -> bool: + return _S_ISMODE(i_mode, S_IFLNK) + +def S_ISREG(i_mode: int) -> bool: + return _S_ISMODE(i_mode, S_IFREG) + +def S_ISDIR(i_mode: int) -> bool: + return _S_ISMODE(i_mode, S_IFDIR) + +def S_ISCHR(i_mode: int) -> bool: + return _S_ISMODE(i_mode, S_IFCHR) + +def S_ISBLK(i_mode: int) -> bool: + return _S_ISMODE(i_mode, S_IFBLK) + +def S_ISFIFO(i_mode: int) -> bool: + return _S_ISMODE(i_mode, S_IFIFO) + +def S_ISSOCK(i_mode: int) -> bool: + return _S_ISMODE(i_mode, S_IFSOCK) + def super_fstype(sb: gdb.Value) -> str: """ Returns the file system type's name for a given superblock. diff --git a/tests/test_subsystem_filesystem.py b/tests/test_subsystem_filesystem.py new file mode 100644 index 00000000000..0b4227bfdda --- /dev/null +++ b/tests/test_subsystem_filesystem.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: +import unittest +import gdb + +import crash.subsystem.filesystem as fs + +class TestSubsystemFilesystem(unittest.TestCase): + def get_bits(self, mode): + v = gdb.Value(mode) + return fs.inode_mode_permission_bits(v) + + def test_mode_regular_file(self): + mode = fs.S_IFREG + self.assertFalse(fs.S_ISLNK(mode)) + self.assertTrue(fs.S_ISREG(mode)) + self.assertFalse(fs.S_ISDIR(mode)) + self.assertFalse(fs.S_ISCHR(mode)) + self.assertFalse(fs.S_ISBLK(mode)) + self.assertFalse(fs.S_ISFIFO(mode)) + self.assertFalse(fs.S_ISSOCK(mode)) + + def test_mode_symbolic_link(self): + mode = fs.S_IFLNK + self.assertTrue(fs.S_ISLNK(mode)) + self.assertFalse(fs.S_ISREG(mode)) + self.assertFalse(fs.S_ISDIR(mode)) + self.assertFalse(fs.S_ISCHR(mode)) + self.assertFalse(fs.S_ISBLK(mode)) + self.assertFalse(fs.S_ISFIFO(mode)) + self.assertFalse(fs.S_ISSOCK(mode)) + + def test_mode_directory(self): + mode = fs.S_IFDIR + self.assertFalse(fs.S_ISLNK(mode)) + self.assertFalse(fs.S_ISREG(mode)) + self.assertTrue(fs.S_ISDIR(mode)) + self.assertFalse(fs.S_ISCHR(mode)) + self.assertFalse(fs.S_ISBLK(mode)) + self.assertFalse(fs.S_ISFIFO(mode)) + self.assertFalse(fs.S_ISSOCK(mode)) + + def test_mode_chardev(self): + mode = fs.S_IFCHR + self.assertFalse(fs.S_ISLNK(mode)) + self.assertFalse(fs.S_ISREG(mode)) + self.assertFalse(fs.S_ISDIR(mode)) + self.assertTrue(fs.S_ISCHR(mode)) + self.assertFalse(fs.S_ISBLK(mode)) + self.assertFalse(fs.S_ISFIFO(mode)) + self.assertFalse(fs.S_ISSOCK(mode)) + + def test_mode_blockdev(self): + mode = fs.S_IFBLK + self.assertFalse(fs.S_ISLNK(mode)) + self.assertFalse(fs.S_ISREG(mode)) + self.assertFalse(fs.S_ISDIR(mode)) + self.assertFalse(fs.S_ISCHR(mode)) + self.assertTrue(fs.S_ISBLK(mode)) + self.assertFalse(fs.S_ISFIFO(mode)) + self.assertFalse(fs.S_ISSOCK(mode)) + + def test_mode_fifo(self): + mode = fs.S_IFIFO + self.assertFalse(fs.S_ISLNK(mode)) + self.assertFalse(fs.S_ISREG(mode)) + self.assertFalse(fs.S_ISDIR(mode)) + self.assertFalse(fs.S_ISCHR(mode)) + self.assertFalse(fs.S_ISBLK(mode)) + self.assertTrue(fs.S_ISFIFO(mode)) + self.assertFalse(fs.S_ISSOCK(mode)) + + def test_mode_socket(self): + mode = fs.S_IFSOCK + self.assertFalse(fs.S_ISLNK(mode)) + self.assertFalse(fs.S_ISREG(mode)) + self.assertFalse(fs.S_ISDIR(mode)) + self.assertFalse(fs.S_ISCHR(mode)) + self.assertFalse(fs.S_ISBLK(mode)) + self.assertFalse(fs.S_ISFIFO(mode)) + self.assertTrue(fs.S_ISSOCK(mode)) + + def test_inode_permission_bits_0________(self): + mode = 0 + perms = self.get_bits(mode) + self.assertTrue(perms == '?---------') + + def test_inode_permission_bits__________(self): + mode = fs.S_IFREG + perms = self.get_bits(mode) + self.assertTrue(perms == '----------') + + def test_inode_permission_bits__r________(self): + mode = fs.S_IFREG|fs.S_IRUSR + perms = self.get_bits(mode) + self.assertTrue(perms == '-r--------') + + def test_inode_permission_bits___w_______(self): + mode = fs.S_IFREG|fs.S_IWUSR + perms = self.get_bits(mode) + self.assertTrue(perms == '--w-------') + + def test_inode_permission_bits___x______(self): + mode = fs.S_IFREG|fs.S_IXUSR + perms = self.get_bits(mode) + self.assertTrue(perms == '---x------') + + def test_inode_permission_bits__rw_______(self): + mode = fs.S_IFREG|fs.S_IRUSR|fs.S_IWUSR + perms = self.get_bits(mode) + self.assertTrue(perms == '-rw-------') + + def test_inode_permission_bits__r_x______(self): + mode = fs.S_IFREG|fs.S_IRUSR|fs.S_IXUSR + perms = self.get_bits(mode) + self.assertTrue(perms == '-r-x------') + + def test_inode_permission_bits__rwx______(self): + mode = fs.S_IFREG|fs.S_IRUSR|fs.S_IWUSR|fs.S_IXUSR + perms = self.get_bits(mode) + self.assertTrue(perms == '-rwx------') + + def test_inode_permission_bits_____r_____(self): + mode = fs.S_IFREG|fs.S_IRGRP + perms = self.get_bits(mode) + self.assertTrue(perms == '----r-----') + + def test_inode_permission_bits______w____(self): + mode = fs.S_IFREG|fs.S_IWGRP + perms = self.get_bits(mode) + self.assertTrue(perms == '-----w----') + + def test_inode_permission_bits______x___(self): + mode = fs.S_IFREG|fs.S_IXGRP + perms = self.get_bits(mode) + self.assertTrue(perms == '------x---') + + def test_inode_permission_bits_____rw____(self): + mode = fs.S_IFREG|fs.S_IRGRP|fs.S_IWGRP + perms = self.get_bits(mode) + self.assertTrue(perms == '----rw----') + + def test_inode_permission_bits_____r_x___(self): + mode = fs.S_IFREG|fs.S_IRGRP|fs.S_IXGRP + perms = self.get_bits(mode) + self.assertTrue(perms == '----r-x---') + + def test_inode_permission_bits_____rwx___(self): + mode = fs.S_IFREG|fs.S_IRGRP|fs.S_IWGRP|fs.S_IXGRP + perms = self.get_bits(mode) + self.assertTrue(perms == '----rwx---') + + def test_inode_permission_bits________r__(self): + mode = fs.S_IFREG|fs.S_IROTH + perms = self.get_bits(mode) + self.assertTrue(perms == '-------r--') + + def test_inode_permission_bits_________w_(self): + mode = fs.S_IFREG|fs.S_IWOTH + perms = self.get_bits(mode) + self.assertTrue(perms == '--------w-') + + def test_inode_permission_bits_________x(self): + mode = fs.S_IFREG|fs.S_IXOTH + perms = self.get_bits(mode) + self.assertTrue(perms == '---------x') + + def test_inode_permission_bits________rw_(self): + mode = fs.S_IFREG|fs.S_IROTH|fs.S_IWOTH + perms = self.get_bits(mode) + self.assertTrue(perms == '-------rw-') + + def test_inode_permission_bits________r_x(self): + mode = fs.S_IFREG|fs.S_IROTH|fs.S_IXOTH + perms = self.get_bits(mode) + self.assertTrue(perms == '-------r-x') + + def test_inode_permission_bits________rwx(self): + mode = fs.S_IFREG|fs.S_IROTH|fs.S_IWOTH|fs.S_IXOTH + perms = self.get_bits(mode) + self.assertTrue(perms == '-------rwx') + + def test_inode_permission_bits__rw_r__r__(self): + mode = fs.S_IFREG|fs.S_IRUSR|fs.S_IWUSR|fs.S_IRGRP|fs.S_IROTH + perms = self.get_bits(mode) + self.assertTrue(perms == '-rw-r--r--') + + def test_inode_permission_bits_drw_r__r__(self): + mode = fs.S_IFDIR|fs.S_IRUSR|fs.S_IWUSR|fs.S_IXUSR + mode |= fs.S_IRGRP|fs.S_IXGRP|fs.S_IROTH|fs.S_IXOTH + perms = self.get_bits(mode) + self.assertTrue(perms == 'drwxr-xr-x') + + def test_inode_permission_bits_srw_r__r__(self): + mode = fs.S_IFSOCK|fs.S_IRUSR|fs.S_IWUSR|fs.S_IRGRP|fs.S_IROTH + perms = self.get_bits(mode) + self.assertTrue(perms == 'srw-r--r--') + + def test_inode_permission_bits_brw_r__r__(self): + mode = fs.S_IFBLK|fs.S_IRUSR|fs.S_IWUSR|fs.S_IRGRP|fs.S_IROTH + perms = self.get_bits(mode) + self.assertTrue(perms == 'brw-r--r--') + + def test_inode_permission_bits_crw_r__r__(self): + mode = fs.S_IFCHR|fs.S_IRUSR|fs.S_IWUSR|fs.S_IRGRP|fs.S_IROTH + perms = self.get_bits(mode) + self.assertTrue(perms == 'crw-r--r--') + + def test_inode_permission_bits_lrwxrwxrwx(self): + mode = fs.S_IFLNK + mode |= fs.S_IRUSR|fs.S_IWUSR|fs.S_IXUSR + mode |= fs.S_IRGRP|fs.S_IWGRP|fs.S_IXGRP + mode |= fs.S_IROTH|fs.S_IWOTH|fs.S_IXOTH + perms = self.get_bits(mode) + self.assertTrue(perms == 'lrwxrwxrwx') + + def test_inode_permission_bits_prw_r__r__(self): + mode = fs.S_IFIFO|fs.S_IRUSR|fs.S_IWUSR|fs.S_IRGRP|fs.S_IROTH + perms = self.get_bits(mode) + self.assertTrue(perms == 'prw-r--r--') + + def test_inode_permission_bits__rwsr_xr_x(self): + mode = fs.S_IFREG|fs.S_IRUSR|fs.S_IWUSR|fs.S_IXUSR|fs.S_ISUID + mode |= fs.S_IRGRP|fs.S_IXGRP|fs.S_IROTH|fs.S_IXOTH + perms = self.get_bits(mode) + self.assertTrue(perms == '-rwsr-xr-x') + + def test_inode_permission_bits__rwSr__r__(self): + mode = fs.S_IFREG|fs.S_IRUSR|fs.S_IWUSR|fs.S_ISUID + mode |= fs.S_IRGRP|fs.S_IROTH + perms = self.get_bits(mode) + self.assertTrue(perms == '-rwSr--r--') + + def test_inode_permission_bits__rwxr_sr_x(self): + mode = fs.S_IFREG|fs.S_IRUSR|fs.S_IWUSR|fs.S_IXUSR + mode |= fs.S_IRGRP|fs.S_IXGRP|fs.S_ISGID|fs.S_IROTH|fs.S_IXOTH + perms = self.get_bits(mode) + self.assertTrue(perms == '-rwxr-sr-x') + + def test_inode_permission_bits__rw_r_Sr__(self): + mode = fs.S_IFREG|fs.S_IRUSR|fs.S_IWUSR + mode |= fs.S_IRGRP|fs.S_ISGID|fs.S_IROTH + perms = self.get_bits(mode) + self.assertTrue(perms == '-rw-r-Sr--') + + def test_inode_permission_bits_drwxrwxrwt(self): + mode = fs.S_IFDIR|fs.S_IRUSR|fs.S_IWUSR|fs.S_IXUSR + mode |= fs.S_IRGRP|fs.S_IWGRP|fs.S_IXGRP + mode |= fs.S_IROTH|fs.S_IWOTH|fs.S_IXOTH|fs.S_ISVTX + perms = self.get_bits(mode) + self.assertTrue(perms == 'drwxrwxrwt') + + def test_inode_permission_bits__rw_r__r_T(self): + mode = fs.S_IFREG|fs.S_IRUSR|fs.S_IWUSR + mode |= fs.S_IRGRP|fs.S_IROTH|fs.S_ISVTX + perms = self.get_bits(mode) + self.assertTrue(perms == '-rw-r--r-T') From 321d72b68c774bbd48b6981a68bde65573949c63 Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Tue, 25 Jun 2019 14:23:04 -0400 Subject: [PATCH 2/5] crash.types.bitmap: add test_bit routine Signed-off-by: Jeff Mahoney --- crash/types/bitmap.py | 44 ++++++++++++++- tests/test_types_bitmap.py | 113 +++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 tests/test_types_bitmap.py diff --git a/crash/types/bitmap.py b/crash/types/bitmap.py index 6f038b72959..5676b8c0740 100644 --- a/crash/types/bitmap.py +++ b/crash/types/bitmap.py @@ -11,7 +11,7 @@ requires that it be of either type. """ -from typing import Iterable +from typing import Iterable, Tuple from crash.exceptions import InvalidArgumentError from crash.util.symbols import Types @@ -30,6 +30,13 @@ def _check_bitmap_type(bitmap: gdb.Value) -> None: raise InvalidArgumentError("bitmaps are expected to be arrays of unsigned long not `{}'" .format(bitmap.type)) +def _get_bit_location(bit: int) -> Tuple[int, int]: + element = bit // (types.unsigned_long_type.sizeof << 3) + offset = bit % (types.unsigned_long_type.sizeof << 3) + + return (element, offset) + + def for_each_set_bit(bitmap: gdb.Value, size_in_bytes: int = None) -> Iterable[int]: """ @@ -211,8 +218,7 @@ def find_next_set_bit(bitmap: gdb.Value, start: int, raise IndexError("Element {} is out of range ({} elements)" .format(start, elements)) - element = start // (types.unsigned_long_type.sizeof << 3) - offset = start % (types.unsigned_long_type.sizeof << 3) + (element, offset) = _get_bit_location(start) for n in range(element, elements): if bitmap[n] == 0: @@ -316,3 +322,35 @@ def find_last_set_bit(bitmap: gdb.Value, size_in_bytes: int = None) -> int: return n * (types.unsigned_long_type.sizeof << 3) + v return 0 + +def test_bit(bitmap: gdb.Value, bit: int, size_in_bytes: int = None) -> bool: + """ + Test a bit in a bitmap. Unlike the ``find`` family of functions, + the index starts at 0. + + Args: + bitmap: The bitmap to use for testing + bit: The bit in the bitmap to test, starting at offset 0 + size_in_bytes (optional, default = None): The size of the bitmap + if a pointer is used. + Returns: + :obj:`bool`: Whether the bit is set or not + + Raises: + :obj:`.InvalidArgumentError`: The :obj:`gdb.Value` is not + of type ``unsigned long[]`` or ``unsigned long *``. + + """ + _check_bitmap_type(bitmap) + + if size_in_bytes is None: + size_in_bytes = bitmap.type.sizeof + + elements = size_in_bytes // types.unsigned_long_type.sizeof + + (element, offset) = _get_bit_location(bit) + + if element >= elements: + raise ValueError(f"bit {bit} is out of range > {size_in_bytes << 3}") + + return (bitmap[element] & (1 << offset)) != 0 diff --git a/tests/test_types_bitmap.py b/tests/test_types_bitmap.py new file mode 100644 index 00000000000..b11cf2d4f93 --- /dev/null +++ b/tests/test_types_bitmap.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: + +import unittest +import sys + +import crash.types.bitmap as bm + +import gdb + +class TestBitmap(unittest.TestCase): + def setUp(self): + gdb.execute("file tests/test-percpu") + ulong = gdb.lookup_type('unsigned long') + ulong_array = ulong.array(0) + + # 10101010010100101010100010101001000101001000100010100101001001001010010 + val = 0x552954548A445292 + + self.bitmap = gdb.Value(val.to_bytes(8, sys.byteorder), ulong_array) + + def test_test_bit(self): + self.assertFalse(bm.test_bit(self.bitmap, 0)) + self.assertTrue(bm.test_bit(self.bitmap, 1)) + self.assertFalse(bm.test_bit(self.bitmap, 2)) + self.assertFalse(bm.test_bit(self.bitmap, 3)) + self.assertTrue(bm.test_bit(self.bitmap, 4)) + self.assertFalse(bm.test_bit(self.bitmap, 5)) + self.assertFalse(bm.test_bit(self.bitmap, 6)) + self.assertTrue(bm.test_bit(self.bitmap, 7)) + self.assertFalse(bm.test_bit(self.bitmap, 8)) + self.assertTrue(bm.test_bit(self.bitmap, 9)) + self.assertFalse(bm.test_bit(self.bitmap, 10)) + self.assertFalse(bm.test_bit(self.bitmap, 11)) + self.assertTrue(bm.test_bit(self.bitmap, 12)) + self.assertFalse(bm.test_bit(self.bitmap, 13)) + self.assertTrue(bm.test_bit(self.bitmap, 14)) + self.assertFalse(bm.test_bit(self.bitmap, 15)) + self.assertFalse(bm.test_bit(self.bitmap, 16)) + self.assertFalse(bm.test_bit(self.bitmap, 17)) + self.assertTrue(bm.test_bit(self.bitmap, 18)) + self.assertFalse(bm.test_bit(self.bitmap, 19)) + self.assertFalse(bm.test_bit(self.bitmap, 20)) + self.assertFalse(bm.test_bit(self.bitmap, 21)) + self.assertTrue(bm.test_bit(self.bitmap, 22)) + self.assertFalse(bm.test_bit(self.bitmap, 23)) + self.assertFalse(bm.test_bit(self.bitmap, 24)) + self.assertTrue(bm.test_bit(self.bitmap, 25)) + self.assertFalse(bm.test_bit(self.bitmap, 26)) + self.assertTrue(bm.test_bit(self.bitmap, 27)) + self.assertFalse(bm.test_bit(self.bitmap, 28)) + self.assertFalse(bm.test_bit(self.bitmap, 29)) + self.assertFalse(bm.test_bit(self.bitmap, 30)) + self.assertTrue(bm.test_bit(self.bitmap, 31)) + self.assertFalse(bm.test_bit(self.bitmap, 32)) + self.assertFalse(bm.test_bit(self.bitmap, 33)) + self.assertTrue(bm.test_bit(self.bitmap, 34)) + self.assertFalse(bm.test_bit(self.bitmap, 35)) + self.assertTrue(bm.test_bit(self.bitmap, 36)) + self.assertFalse(bm.test_bit(self.bitmap, 37)) + self.assertTrue(bm.test_bit(self.bitmap, 38)) + self.assertFalse(bm.test_bit(self.bitmap, 39)) + self.assertFalse(bm.test_bit(self.bitmap, 40)) + self.assertFalse(bm.test_bit(self.bitmap, 41)) + self.assertTrue(bm.test_bit(self.bitmap, 42)) + self.assertFalse(bm.test_bit(self.bitmap, 43)) + self.assertTrue(bm.test_bit(self.bitmap, 44)) + self.assertFalse(bm.test_bit(self.bitmap, 45)) + self.assertTrue(bm.test_bit(self.bitmap, 46)) + self.assertFalse(bm.test_bit(self.bitmap, 47)) + self.assertTrue(bm.test_bit(self.bitmap, 48)) + self.assertFalse(bm.test_bit(self.bitmap, 49)) + self.assertFalse(bm.test_bit(self.bitmap, 50)) + self.assertTrue(bm.test_bit(self.bitmap, 51)) + self.assertFalse(bm.test_bit(self.bitmap, 52)) + self.assertTrue(bm.test_bit(self.bitmap, 53)) + self.assertFalse(bm.test_bit(self.bitmap, 54)) + self.assertFalse(bm.test_bit(self.bitmap, 55)) + self.assertTrue(bm.test_bit(self.bitmap, 56)) + self.assertFalse(bm.test_bit(self.bitmap, 57)) + self.assertTrue(bm.test_bit(self.bitmap, 58)) + self.assertFalse(bm.test_bit(self.bitmap, 59)) + self.assertTrue(bm.test_bit(self.bitmap, 60)) + self.assertFalse(bm.test_bit(self.bitmap, 61)) + self.assertTrue(bm.test_bit(self.bitmap, 62)) + self.assertFalse(bm.test_bit(self.bitmap, 63)) + + def test_for_each_set_bit(self): + count = 0 + for bit in bm.for_each_set_bit(self.bitmap): + count += 1 + + self.assertTrue(count == 24) + + def test_find_first_set_bit(self): + bit = bm.find_first_set_bit(self.bitmap) + self.assertTrue(bit == 2) + + def test_find_first_zero_bit(self): + bit = bm.find_first_zero_bit(self.bitmap) + self.assertTrue(bit == 1) + + def test_find_next_set_bit(self): + bit = bm.find_next_set_bit(self.bitmap, 27) + self.assertTrue(bit == 28) + + def test_find_next_zero_bit(self): + bit = bm.find_next_zero_bit(self.bitmap, 51) + self.assertTrue(bit == 53) + + def test_find_last_set_bit(self): + bit = bm.find_last_set_bit(self.bitmap) + self.assertTrue(bit == 63) From 1b6ce2987ffb2265c9951736a1318a912ddc1469 Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Wed, 26 Jun 2019 08:29:53 -0400 Subject: [PATCH 3/5] docs: fix typo in development.rst Signed-off-by: Jeff Mahoney --- doc-source/development.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc-source/development.rst b/doc-source/development.rst index 8768eb31897..ae9f75c0452 100644 --- a/doc-source/development.rst +++ b/doc-source/development.rst @@ -12,7 +12,7 @@ Development gdb-internals -Documentation is automatically build from the python code for the user +Documentation is automatically built from the python code for the user guide, command help text, and API reference. There are several make targets to assist in your development efforts: From 82b64663c73d9e0365c87a91461809f02e840ba0 Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Wed, 26 Jun 2019 08:53:08 -0400 Subject: [PATCH 4/5] docs: add page on development patterns I've fielded questions about some review feedback that involve code patterns that are acceptable for the project. This page will contain the documentation for those as it's created. Signed-off-by: Jeff Mahoney --- doc-source/development.rst | 1 + doc-source/patterns.rst | 86 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 doc-source/patterns.rst diff --git a/doc-source/development.rst b/doc-source/development.rst index ae9f75c0452..9d26f65fd6c 100644 --- a/doc-source/development.rst +++ b/doc-source/development.rst @@ -10,6 +10,7 @@ Development crash/modules gdb-internals + patterns Documentation is automatically built from the python code for the user diff --git a/doc-source/patterns.rst b/doc-source/patterns.rst new file mode 100644 index 00000000000..1189eb9f201 --- /dev/null +++ b/doc-source/patterns.rst @@ -0,0 +1,86 @@ +Patterns +======== + +Optional error handling +----------------------- + +In some cases it may be desirable to keep exception handling in a helper +that returns :obj:`None` on error. In the past, the project used an +optional ``error`` argument that defaulted to :obj:`True` that indicated +that exceptions should be raised. Callers could pass ``error=False`` to +instruct the function to return :obj:`None` instead. + +With Python's +`typing `_ +annotations, these routines must be annotated as returning an +`Optional `_ +value. While the +`@overload `_ +decorator allows us to associate return types with specific argument types +and counts, there is no way to associate a return type with specific +argument `values`, like ``error=False``. + +A function annotated as returning an ``Optional`` value affects the implied +types of the variables used to assign the result. Every caller of such +a routine would need to check the result against :obj:`None` in order to +drop the ``Optional`` annotation from the type. Even when we know the +function `cannot` return :obj:`None` when passed ``error=True``. + +The way we handle this is to have separate functions for each case +so that callers which will never have a :obj:`None` value returned +do not need to check it. + +Here are a few examples: + + +Function raises its own exceptions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: py + + from typing import Optional + + import gdb + + def new_routine(val: gdb.Value) -> str: + if some_condition: + raise RuntimeError("something bad happened") + + return val.string() + + def new_routine_safe(val: gdb.Value) -> Optional[str]: + try: + return new_routine(val) + except RuntimeError: + return None + + +Function calls functions that raise optional exceptions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: py + + from typing import Optional + + import gdb + + def some_existing_routine(val: gdb.Value, error: bool = True) -> Optional[str]: + if some_condition: + if error: + raise RuntimeError("something bad happened") + return None + + return val.string() + + def new_routine(val: gdb.Value) -> str: + print("do something") + + ret = some_existing_routine(val) + + # This is required to drop the Optional annotation + if ret is None: + raise RuntimeError("some_existing_routine can't return None") + return ret + + def new_routine_safe(val: gdb.Value) -> Optional[str]: + return some_existing_routine(val, False) From 03f5e20ee4d786e9df69755cd731fb8680c6ec3f Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Wed, 26 Jun 2019 09:35:01 -0400 Subject: [PATCH 5/5] docs: move command documentation above the delayed lookup explanation The example doesn't demonstrate the command documentation so it's confusing to have it be the last thing before the example. Signed-off-by: Jeff Mahoney --- doc-source/api_changes.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc-source/api_changes.rst b/doc-source/api_changes.rst index aefdbe089c1..6a66c85e32f 100644 --- a/doc-source/api_changes.rst +++ b/doc-source/api_changes.rst @@ -41,6 +41,11 @@ Public / Protected Namespace The use of ``_`` as a prefix for protected members of classes is now expected and will be enforced during ``make test`` if `pylint `_ is installed. In the example below, several internal members and methods of `MyClass` have been renamed to indicate that they are protected. +Command documentation +--------------------- + +In earlier versions of crash-python, commands were documented using the docstring of the Command itself. This has changed to use the docstring of the module instead. More details can be found in :class:`~crash.command.Command` and :class:`~crash.command.ArgumentParser`. The format of the docstring is `reStructuredText `_ and is parsed using `Sphinx `_. The documentation is used for both the user guide and the application command help. This is an area that is subject to change in the future. + New mechanism for delayed lookups --------------------------------- @@ -54,11 +59,6 @@ The current version of crash-python uses the :class:`crash.util.symbol` module t - There are accessors beyond attributes. The :class:`.DelayedCollection` family of classes all have :meth:`~.DelayedCollection.__getattr__`, :meth:`~DelayedCollection.__getitem__`, and :meth:`~DelayedCollection.get` defined, so they can be accessed as attribute names, dictionary keys, or by function call. The latter two can be used with any name, but the attribute names cannot be used for symbols that start with ``__``. -Command documentation ---------------------- - -In earlier versions of crash-python, commands were documented using the docstring of the Command itself. This has changed to use the docstring of the module instead. More details can be found in :class:`~crash.command.Command` and :class:`~crash.command.ArgumentParser`. The format of the docstring is `reStructuredText `_ and is parsed using `Sphinx `_. The documentation is used for both the user guide and the application command help. This is an area that is subject to change in the future. - Example -------