unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#74738: 31.0.50; Freezes in Python-mode on some Python file when searching or scrolling
@ 2024-12-08 13:05 rehan malak
  2024-12-08 17:37 ` Eli Zaretskii
  0 siblings, 1 reply; 5+ messages in thread
From: rehan malak @ 2024-12-08 13:05 UTC (permalink / raw)
  To: 74738

[-- Attachment #1: Type: text/plain, Size: 8963 bytes --]

Hi,
I can reproduce systematically this freeze dealing with a 10000lines
Python file :

wget https://raw.githubusercontent.com/hugsy/gef/refs/heads/main/gef.py
emacs -Q gef.py

PageDown several times or scrolling with the mouse

Emacs freezes, Ctrl-g not working, CPU 100%

It works also by searching : Ctrl-s show RET
then Ctrl-s several times




With a minimal .emacs :

(set debug-on-error t)
(set debug-on-quit t)

and before scrolling

M-x profiler-start RET

then in an external terminal

pkill -SIGUSR2 emacs

I get the backtrace :

Debugger entered--entering a function:
* #f(compiled-function () #<bytecode -0x179dcd1db31182ae>)()
  syntax-ppss()
  python-syntax-context-type()
  python-nav-forward-block(-1)
  python-nav-backward-block()
  python-nav-beginning-of-block()
  python-nav-end-of-block()
  python-info-statement-ends-block-p()
  python-info-end-of-block-p()
  python-nav--forward-sexp(1 nil nil)
  python-nav-forward-sexp(1)
  forward-sexp(1)
  up-list(1)
  python--font-lock-f-strings(30419)
  font-lock-fontify-keywords-region(28914 30419 nil)
  font-lock-default-fontify-region(28914 30414 nil)
  font-lock-fontify-region(28914 30414)
  #f(compiled-function (fun) #<bytecode
-0x8a96d65e1315e71>)(font-lock-fontify-region)
  run-hook-wrapped(#f(compiled-function (fun) #<bytecode
-0x8a96d65e1315e71>) font-lock-fontify-region)
  jit-lock--run-functions(28914 30414)
  jit-lock-fontify-now(28914 30414)
  jit-lock-function(28914)

and can look at profiler report

M-x profiler-stop
M-x profiler-report

showing time spent in syntax-ppss


       17770  88% - jit-lock-function
       17770  88%  - jit-lock-fontify-now
       17770  88%   - jit-lock--run-functions
       17770  88%    - run-hook-wrapped
       17770  88%     - #<byte-code-function FA0>
       17770  88%      - font-lock-fontify-region
       17770  88%       - font-lock-default-fontify-region
       17690  87%        - font-lock-fontify-keywords-region
       17678  87%         - python--font-lock-f-strings
       17674  87%          - up-list
       17674  87%           - forward-sexp
       17674  87%            - python-nav-forward-sexp
       17674  87%             - python-nav--forward-sexp
        9253  45%              - python-info-end-of-block-p
        9245  45%               - python-info-statement-ends-block-p
        9221  45%                - python-nav-end-of-block
        8609  42%                 - python-nav-beginning-of-block
        8557  42%                  - python-nav-backward-block
        8557  42%                   - python-nav-forward-block
        8005  39%                    - python-syntax-context-type
        7997  39%                     - syntax-ppss
        7097  35%                        parse-partial-sexp
         753   3%                      + #<byte-code-function A23>
         376   1%                    + python-nav-beginning-of-statement
         132   0%                      re-search-backward
          24   0%                      looking-at
          28   0%                    current-indentation
           8   0%                  + python-nav-beginning-of-statement
           4   0%                    python-info-current-line-empty-p
           4   0%                  + python-info-current-line-comment-p
         604   2%                 + python-nav-end-of-statement
           4   0%                 + python-info-current-line-comment-p
           4   0%                   current-indentation
          24   0%                + python-nav-end-of-statement
           8   0%               + python-info-end-of-statement-p
        8178  40%              - python-info-statement-ends-block-p
        8150  40%               - python-nav-end-of-block
        7754  38%                - python-nav-beginning-of-block
        7738  38%                 - python-nav-backward-block
        7734  38%                  - python-nav-forward-block
        7178  35%                   - python-syntax-context-type
        7174  35%                    - syntax-ppss
        6978  34%                       parse-partial-sexp
           8   0%                       make-closure
           8   0%                       #<byte-code-function B1E>
           4   0%                       syntax-table
           4   0%                       syntax-ppss--update-stats
         412   2%                   + python-nav-beginning-of-statement
          92   0%                     re-search-backward
          44   0%                     looking-at
          12   0%                   current-indentation
           4   0%                   looking-at
         380   1%                + python-nav-end-of-statement
          16   0%                + python-util-forward-comment
          28   0%               + python-nav-end-of-statement
         151   0%              + python-nav-end-of-block
          28   0%              + python-info-beginning-of-block-p
          24   0%              + python-info-end-of-statement-p
          20   0%              + python-info-beginning-of-statement-p
           8   0%              + python-syntax-context-type
           8   0%              + python-info-statement-starts-block-p
           4   0%          + syntax-ppss
           8   0%         + #<byte-code-function 7E0>
           4   0%         + #<byte-code-function 740>
          80   0%        + font-lock-fontify-syntactically-region
        2279  11%   Automatic GC
          73   0% + redisplay_internal (C function)
          40   0% + command-execute
           8   0% + ...
           4   0% + mouse--click-1-maybe-follows-link

other people talking about python/profiling/syntax-ppss :

https://www.reddit.com/r/emacs/comments/z0oye9/emacs_freezes_when_opening_a_python_file/
https://www.reddit.com/r/emacs/comments/9h7onq/large_python_files_hanging_with_an_unmatched_quote/

Please tell me how I can help ?

Please tell me if there is a workaround without losing python syntax color ?

Thanks

Rehan



In GNU Emacs 31.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version
 3.24.43, cairo version 1.18.2) of 2024-12-08 built on cndl
Repository revision: b953bcb17047998c9e41cede7c5e5ffec22209b2
Repository branch: master
System Description: Debian GNU/Linux trixie/sid

Configured using:
 'configure --prefix=/home/jean/local/emacs --with-pgtk'

Configured features:
CAIRO DBUS FREETYPE GLIB GMP GNUTLS GSETTINGS HARFBUZZ JPEG LIBSELINUX
LIBXML2 MODULES NOTIFY INOTIFY PDUMPER PGTK PNG SECCOMP SOUND THREADS
TIFF TOOLKIT_SCROLL_BARS WEBP XIM GTK3 ZLIB

Important settings:
  value of $LANG: en_US.UTF-8
  value of $XMODIFIERS: @im=ibus
  locale-coding-system: utf-8-unix

Major mode: Python

Minor modes in effect:
  tooltip-mode: t
  global-eldoc-mode: t
  eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  tool-bar-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  minibuffer-regexp-mode: t
  line-number-mode: t
  transient-mark-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t

Load-path shadows:
None found.

Features:
(shadow sort mail-extr emacsbug message mailcap yank-media puny dired
dired-loaddefs rfc822 mml mml-sec password-cache epa derived epg rfc6068
epg-config gnus-util text-property-search time-date mm-decode mm-bodies
mm-encode mail-parse rfc2231 mailabbrev gmm-utils mailheader sendmail
rfc2047 rfc2045 ietf-drums mm-util mail-prsvr mail-utils cl-seq python
rx project byte-opt gv bytecomp byte-compile compat pcase treesit comint
subr-x ansi-osc ring cl-loaddefs cl-lib ansi-color rmc iso-transl
tooltip cconv eldoc paren electric uniquify ediff-hook vc-hooks
lisp-float-type elisp-mode mwheel term/pgtk-win pgtk-win term/common-win
touch-screen pgtk-dnd tool-bar dnd fontset image regexp-opt fringe
tabulated-list replace newcomment text-mode lisp-mode prog-mode register
page tab-bar menu-bar rfn-eshadow isearch easymenu timer select
scroll-bar mouse jit-lock font-lock syntax font-core term/tty-colors
frame minibuffer nadvice seq simple cl-generic indonesian philippine
cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao
korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech
european ethiopic indian cyrillic chinese composite emoji-zwj charscript
charprop case-table epa-hook jka-cmpr-hook help abbrev obarray oclosure
cl-preloaded button loaddefs theme-loaddefs faces cus-face macroexp
files window text-properties overlay sha1 md5 base64 format env
code-pages mule custom widget keymap hashtable-print-readable backquote
threads dbusbind inotify dynamic-setting system-font-setting
font-render-setting cairo gtk pgtk multi-tty move-toolbar
make-network-process emacs)

Memory information:
((conses 16 60995 13527) (symbols 48 7517 0) (strings 32 19122 1493)
 (string-bytes 1 515162) (vectors 16 13554)
 (vector-slots 8 145472 8433) (floats 8 26 2) (intervals 56 436 0)
 (buffers 984 12))

[-- Attachment #2: Type: text/html, Size: 10952 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* bug#74738: 31.0.50; Freezes in Python-mode on some Python file when searching or scrolling
  2024-12-08 13:05 bug#74738: 31.0.50; Freezes in Python-mode on some Python file when searching or scrolling rehan malak
@ 2024-12-08 17:37 ` Eli Zaretskii
  2024-12-09 14:58   ` rehan malak
  2024-12-09 15:04   ` kobarity
  0 siblings, 2 replies; 5+ messages in thread
From: Eli Zaretskii @ 2024-12-08 17:37 UTC (permalink / raw)
  To: rehan malak, Stefan Monnier, kobarity; +Cc: 74738

> From: rehan malak <rehan.malak@gmail.com>
> Date: Sun, 8 Dec 2024 14:05:36 +0100
> 
> I can reproduce systematically this freeze dealing with a 10000lines
> Python file :
> 
> wget https://raw.githubusercontent.com/hugsy/gef/refs/heads/main/gef.py
> emacs -Q gef.py

A much smaller reproducer is attached below.  Just visiting it freezes
Emacs.  But if I change the line marked below:

    @classmethod
    def is_valid(cls, _: pathlib.Path) -> bool:
        raise NotImplementedError

    def __str__(self) -> str:
  >>>>> return f"{self.name}('{self.path.absolute()}', entry @ {self.entry_point:#x})"

to say this instead:

        return self.name

the problem goes away.  So it is something in that complex expression
that trips syntax-ppss.

Stefan and kobarity, any suggestions or ideas?

> PageDown several times or scrolling with the mouse
> 
> Emacs freezes, Ctrl-g not working, CPU 100%
> 
> It works also by searching : Ctrl-s show RET
> then Ctrl-s several times
> 
> With a minimal .emacs :
> 
> (set debug-on-error t) 
> (set debug-on-quit t) 
> 
> and before scrolling
> 
> M-x profiler-start RET
> 
> then in an external terminal
> 
> pkill -SIGUSR2 emacs
> 
> I get the backtrace :
> 
> Debugger entered--entering a function:
> * #f(compiled-function () #<bytecode -0x179dcd1db31182ae>)()
>   syntax-ppss()
>   python-syntax-context-type()
>   python-nav-forward-block(-1)
>   python-nav-backward-block()
>   python-nav-beginning-of-block()
>   python-nav-end-of-block()
>   python-info-statement-ends-block-p()
>   python-info-end-of-block-p()
>   python-nav--forward-sexp(1 nil nil)
>   python-nav-forward-sexp(1)
>   forward-sexp(1)
>   up-list(1)
>   python--font-lock-f-strings(30419)
>   font-lock-fontify-keywords-region(28914 30419 nil)
>   font-lock-default-fontify-region(28914 30414 nil)
>   font-lock-fontify-region(28914 30414)
>   #f(compiled-function (fun) #<bytecode -0x8a96d65e1315e71>)(font-lock-fontify-region)
>   run-hook-wrapped(#f(compiled-function (fun) #<bytecode -0x8a96d65e1315e71>) font-lock-fontify-region)
>   jit-lock--run-functions(28914 30414)
>   jit-lock-fontify-now(28914 30414)
>   jit-lock-function(28914)

I see something different, in GDB:

  Lisp Backtrace:
  "parse-partial-sexp" (0x9f1a788)
  "syntax-ppss" (0x9f1a710)
  "python-info-line-ends-backslash-p" (0x9f1a6b8)
  "python-nav-end-of-statement" (0x9f1a670)
  "python-nav-end-of-block" (0x9f1a630)
  "python-info-statement-ends-block-p" (0x9f1a610)
  "python-info-end-of-block-p" (0x9f1a5c0)
  "python-nav--forward-sexp" (0x9f1a548)
  "python-nav-forward-sexp" (0x9f1a4f8)
  "forward-sexp" (0x9f1a4a8)
  "up-list" (0x9f1a438)
  "python--font-lock-f-strings" (0x9f1a3a0)
  "font-lock-fontify-keywords-region" (0x9f1a308)
  "font-lock-default-fontify-region" (0x9f1a2a0)
  "font-lock-fontify-region" (0x9f1a230)
  0xb9117d0 PVEC_CLOSURE
  "run-hook-wrapped" (0x9f1a1c0)
  "jit-lock--run-functions" (0x9f1a0e8)
  "jit-lock-fontify-now" (0x9f1a058)
  "jit-lock-function" (0x5ffba98)
  "redisplay_internal (C function)" (0x0)

Here's the file with which I can reproduce the problem?


class FileFormat:
    name: str
    path: pathlib.Path
    entry_point: int
    checksec: dict[str, bool]
    sections: list[FileFormatSection]

    def __init__(self, path: str | pathlib.Path) -> None:
        raise NotImplementedError

    def __init_subclass__(cls: Type["FileFormat"], **kwargs):
        global __registered_file_formats__
        super().__init_subclass__(**kwargs)
        required_attributes = ("name", "entry_point", "is_valid", "checksec",)
        for attr in required_attributes:
            if not hasattr(cls, attr):
                raise NotImplementedError(f"File format '{cls.__name__}' is invalid: missing attribute '{attr}'")
        __registered_file_formats__.add(cls)
        return

    @classmethod
    def is_valid(cls, _: pathlib.Path) -> bool:
        raise NotImplementedError

    def __str__(self) -> str:
        return f"{self.name}('{self.path.absolute()}', entry @ {self.entry_point:#x})"


class Elf(FileFormat):
    """Basic ELF parsing.
    Ref:
    - http://www.skyfree.org/linux/references/ELF_Format.pdf
    - https://refspecs.linuxfoundation.org/elf/elfspec_ppc.pdf
    - https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html
    """
    class Class(enum.Enum):
        ELF_32_BITS       = 0x01
        ELF_64_BITS       = 0x02

    ELF_MAGIC         = 0x7f454c46

    class Abi(enum.Enum):
        X86_64            = 0x3e
        X86_32            = 0x03
        ARM               = 0x28
        MIPS              = 0x08
        POWERPC           = 0x14
        POWERPC64         = 0x15
        SPARC             = 0x02
        SPARC64           = 0x2b
        AARCH64           = 0xb7
        RISCV             = 0xf3
        IA64              = 0x32
        M68K              = 0x04

    class Type(enum.Enum):
        ET_RELOC          = 1
        ET_EXEC           = 2
        ET_DYN            = 3
        ET_CORE           = 4

    class OsAbi(enum.Enum):
        SYSTEMV     = 0x00
        HPUX        = 0x01
        NETBSD      = 0x02
        LINUX       = 0x03
        SOLARIS     = 0x06
        AIX         = 0x07
        IRIX        = 0x08
        FREEBSD     = 0x09
        OPENBSD     = 0x0C

    e_magic: int                = ELF_MAGIC
    e_class: "Elf.Class"        = Class.ELF_32_BITS
    e_endianness: Endianness    = Endianness.LITTLE_ENDIAN
    e_eiversion: int
    e_osabi: "Elf.OsAbi"
    e_abiversion: int
    e_pad: bytes
    e_type: "Elf.Type"          = Type.ET_EXEC
    e_machine: Abi              = Abi.X86_32
    e_version: int
    e_entry: int
    e_phoff: int
    e_shoff: int
    e_flags: int
    e_ehsize: int
    e_phentsize: int
    e_phnum: int
    e_shentsize: int
    e_shnum: int
    e_shstrndx: int

    path: pathlib.Path
    phdrs : list["Phdr"]
    shdrs : list["Shdr"]
    name: str = "ELF"

    __checksec : dict[str, bool]

    def __init__(self, path: str | pathlib.Path) -> None:
        """Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown."""

        if isinstance(path, str):
            self.path = pathlib.Path(path).expanduser()
        elif isinstance(path, pathlib.Path):
            self.path = path
        else:
            raise TypeError

        if not self.path.exists():
            raise FileNotFoundError(f"'{self.path}' not found/readable, most gef features will not work")

        self.__checksec = {}

        with self.path.open("rb") as self.fd:
            # off 0x0
            self.e_magic, e_class, e_endianness, self.e_eiversion = self.read_and_unpack(">IBBB")
            if self.e_magic != Elf.ELF_MAGIC:
                # The ELF is corrupted, GDB won't handle it, no point going further
                raise RuntimeError("Not a valid ELF file (magic)")

            self.e_class, self.e_endianness = Elf.Class(e_class), Endianness(e_endianness)

            if self.e_endianness != gef.arch.endianness:
                warn("Unexpected endianness for architecture")

            endian = self.e_endianness

            # off 0x7
            e_osabi, self.e_abiversion = self.read_and_unpack(f"{endian}BB")
            self.e_osabi = Elf.OsAbi(e_osabi)

            # off 0x9
            self.e_pad = self.read(7)

            # off 0x10
            e_type, e_machine, self.e_version = self.read_and_unpack(f"{endian}HHI")
            self.e_type, self.e_machine = Elf.Type(e_type), Elf.Abi(e_machine)

            # off 0x18
            if self.e_class == Elf.Class.ELF_64_BITS:
                self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}QQQ")
            else:
                self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}III")

            self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = self.read_and_unpack(f"{endian}IHHH")
            self.e_shentsize, self.e_shnum, self.e_shstrndx = self.read_and_unpack(f"{endian}HHH")

            self.phdrs = []
            for i in range(self.e_phnum):
                self.phdrs.append(Phdr(self, self.e_phoff + self.e_phentsize * i))

            self.shdrs = []
            for i in range(self.e_shnum):
                self.shdrs.append(Shdr(self, self.e_shoff + self.e_shentsize * i))
        return

    def read(self, size: int) -> bytes:
        return self.fd.read(size)

    def read_and_unpack(self, fmt: str) -> tuple[Any, ...]:
        size = struct.calcsize(fmt)
        data = self.fd.read(size)
        return struct.unpack(fmt, data)

    def seek(self, off: int) -> None:
        self.fd.seek(off, 0)

    def __str__(self) -> str:
        return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})"

    def __repr__(self) -> str:
        return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})"

    @property
    def entry_point(self) -> int:
        return self.e_entry

    @classmethod
    def is_valid(cls, path: pathlib.Path) -> bool:
        return u32(path.open("rb").read(4), e = Endianness.BIG_ENDIAN) == Elf.ELF_MAGIC

    @property
    def checksec(self) -> dict[str, bool]:
        """Check the security property of the ELF binary. The following properties are:
        - Canary
        - NX
        - PIE
        - Fortify
        - Partial/Full RelRO.
        Return a dict() with the different keys mentioned above, and the boolean
        associated whether the protection was found."""
        if not self.__checksec:
            def __check_security_property(opt: str, filename: str, pattern: str) -> bool:
                cmd   = [readelf,]
                cmd  += opt.split()
                cmd  += [filename,]
                lines = gef_execute_external(cmd, as_list=True)
                for line in lines:
                    if re.search(pattern, line):
                        return True
                return False

            abspath = str(self.path.absolute())
            readelf = gef.session.constants["readelf"]
            self.__checksec["Canary"] = __check_security_property("-rs", abspath, r"__stack_chk_fail") is True
            has_gnu_stack = __check_security_property("-W -l", abspath, r"GNU_STACK") is True
            if has_gnu_stack:
                self.__checksec["NX"] = __check_security_property("-W -l", abspath, r"GNU_STACK.*RWE") is False
            else:
                self.__checksec["NX"] = False
            self.__checksec["PIE"] = __check_security_property("-h", abspath, r":.*EXEC") is False
            self.__checksec["Fortify"] = __check_security_property("-s", abspath, r"_chk@GLIBC") is True
            self.__checksec["Partial RelRO"] = __check_security_property("-l", abspath, r"GNU_RELRO") is True
            self.__checksec["Full RelRO"] = self.__checksec["Partial RelRO"] and __check_security_property("-d", abspath, r"BIND_NOW") is True
        return self.__checksec

    @classproperty
    @deprecated("use `Elf.Abi.X86_64`")
    def X86_64(cls) -> int: return Elf.Abi.X86_64.value # pylint: disable=no-self-argument

    @classproperty
    @deprecated("use `Elf.Abi.X86_32`")
    def X86_32(cls) -> int : return Elf.Abi.X86_32.value # pylint: disable=no-self-argument

    @classproperty
    @deprecated("use `Elf.Abi.ARM`")
    def ARM(cls) -> int : return Elf.Abi.ARM.value # pylint: disable=no-self-argument

    @classproperty
    @deprecated("use `Elf.Abi.MIPS`")
    def MIPS(cls) -> int : return Elf.Abi.MIPS.value # pylint: disable=no-self-argument

    @classproperty
    @deprecated("use `Elf.Abi.POWERPC`")
    def POWERPC(cls) -> int : return Elf.Abi.POWERPC.value # pylint: disable=no-self-argument

    @classproperty
    @deprecated("use `Elf.Abi.POWERPC64`")
    def POWERPC64(cls) -> int : return Elf.Abi.POWERPC64.value # pylint: disable=no-self-argument

    @classproperty
    @deprecated("use `Elf.Abi.SPARC`")
    def SPARC(cls) -> int : return Elf.Abi.SPARC.value # pylint: disable=no-self-argument

    @classproperty
    @deprecated("use `Elf.Abi.SPARC64`")
    def SPARC64(cls) -> int : return Elf.Abi.SPARC64.value # pylint: disable=no-self-argument

    @classproperty
    @deprecated("use `Elf.Abi.AARCH64`")
    def AARCH64(cls) -> int : return Elf.Abi.AARCH64.value  # pylint: disable=no-self-argument

    @classproperty
    @deprecated("use `Elf.Abi.RISCV`")
    def RISCV(cls) -> int : return Elf.Abi.RISCV.value # pylint: disable=no-self-argument






^ permalink raw reply	[flat|nested] 5+ messages in thread

* bug#74738: 31.0.50; Freezes in Python-mode on some Python file when searching or scrolling
  2024-12-08 17:37 ` Eli Zaretskii
@ 2024-12-09 14:58   ` rehan malak
  2024-12-11 14:24     ` kobarity
  2024-12-09 15:04   ` kobarity
  1 sibling, 1 reply; 5+ messages in thread
From: rehan malak @ 2024-12-09 14:58 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: kobarity, Stefan Monnier, 74738

[-- Attachment #1: Type: text/plain, Size: 14703 bytes --]

Hi,

Deleting all the "#" in the f-string of the gef.py file removes the problem
on my side.
 sed -i -e 's/#0{align/0{align/g' gef.py
 sed -i -e 's/#0{width/0{width/g' gef.py
 sed -i -e 's/#07x}/07x}/g' gef.py
 sed -i -e 's/#06x}/06x}/g' gef.py
 sed -i -e 's/#04x}/04x}/g' gef.py
 sed -i -e 's/#4x}/4x}/g' gef.py
 sed -i -e 's/#8x}/8x}/g' gef.py
 sed -i -e 's/#10x}/10x}/g' gef.py
 sed -i -e 's/#x}/x}/g' gef.py

Your smaller example Eli also contains a f-string with the "#".

This python f-string format specifier is described here:
https://docs.python.org/3/library/string.html#format-specification-mini-language

Is this format specifier supported from the beginning ?

For example :

value = 0xab
print(f"{value:x} is a value")

=> color syntax : *is a value*    has the color defined by
font-lock-string-face

while

value = 0xab
print(f"{value:#x} is a value")

=> color are messed up... *is* has color defined by font-lock-keyword-face
and *a value* has color defined by default face

Best,

Rehan

On Sun, Dec 8, 2024 at 6:37 PM Eli Zaretskii <eliz@gnu.org> wrote:

> > From: rehan malak <rehan.malak@gmail.com>
> > Date: Sun, 8 Dec 2024 14:05:36 +0100
> >
> > I can reproduce systematically this freeze dealing with a 10000lines
> > Python file :
> >
> > wget https://raw.githubusercontent.com/hugsy/gef/refs/heads/main/gef.py
> > emacs -Q gef.py
>
> A much smaller reproducer is attached below.  Just visiting it freezes
> Emacs.  But if I change the line marked below:
>
>     @classmethod
>     def is_valid(cls, _: pathlib.Path) -> bool:
>         raise NotImplementedError
>
>     def __str__(self) -> str:
>   >>>>> return f"{self.name}('{self.path.absolute()}', entry @
> {self.entry_point:#x})"
>
> to say this instead:
>
>         return self.name
>
> the problem goes away.  So it is something in that complex expression
> that trips syntax-ppss.
>
> Stefan and kobarity, any suggestions or ideas?
>
> > PageDown several times or scrolling with the mouse
> >
> > Emacs freezes, Ctrl-g not working, CPU 100%
> >
> > It works also by searching : Ctrl-s show RET
> > then Ctrl-s several times
> >
> > With a minimal .emacs :
> >
> > (set debug-on-error t)
> > (set debug-on-quit t)
> >
> > and before scrolling
> >
> > M-x profiler-start RET
> >
> > then in an external terminal
> >
> > pkill -SIGUSR2 emacs
> >
> > I get the backtrace :
> >
> > Debugger entered--entering a function:
> > * #f(compiled-function () #<bytecode -0x179dcd1db31182ae>)()
> >   syntax-ppss()
> >   python-syntax-context-type()
> >   python-nav-forward-block(-1)
> >   python-nav-backward-block()
> >   python-nav-beginning-of-block()
> >   python-nav-end-of-block()
> >   python-info-statement-ends-block-p()
> >   python-info-end-of-block-p()
> >   python-nav--forward-sexp(1 nil nil)
> >   python-nav-forward-sexp(1)
> >   forward-sexp(1)
> >   up-list(1)
> >   python--font-lock-f-strings(30419)
> >   font-lock-fontify-keywords-region(28914 30419 nil)
> >   font-lock-default-fontify-region(28914 30414 nil)
> >   font-lock-fontify-region(28914 30414)
> >   #f(compiled-function (fun) #<bytecode
> -0x8a96d65e1315e71>)(font-lock-fontify-region)
> >   run-hook-wrapped(#f(compiled-function (fun) #<bytecode
> -0x8a96d65e1315e71>) font-lock-fontify-region)
> >   jit-lock--run-functions(28914 30414)
> >   jit-lock-fontify-now(28914 30414)
> >   jit-lock-function(28914)
>
> I see something different, in GDB:
>
>   Lisp Backtrace:
>   "parse-partial-sexp" (0x9f1a788)
>   "syntax-ppss" (0x9f1a710)
>   "python-info-line-ends-backslash-p" (0x9f1a6b8)
>   "python-nav-end-of-statement" (0x9f1a670)
>   "python-nav-end-of-block" (0x9f1a630)
>   "python-info-statement-ends-block-p" (0x9f1a610)
>   "python-info-end-of-block-p" (0x9f1a5c0)
>   "python-nav--forward-sexp" (0x9f1a548)
>   "python-nav-forward-sexp" (0x9f1a4f8)
>   "forward-sexp" (0x9f1a4a8)
>   "up-list" (0x9f1a438)
>   "python--font-lock-f-strings" (0x9f1a3a0)
>   "font-lock-fontify-keywords-region" (0x9f1a308)
>   "font-lock-default-fontify-region" (0x9f1a2a0)
>   "font-lock-fontify-region" (0x9f1a230)
>   0xb9117d0 PVEC_CLOSURE
>   "run-hook-wrapped" (0x9f1a1c0)
>   "jit-lock--run-functions" (0x9f1a0e8)
>   "jit-lock-fontify-now" (0x9f1a058)
>   "jit-lock-function" (0x5ffba98)
>   "redisplay_internal (C function)" (0x0)
>
> Here's the file with which I can reproduce the problem?
>
>
> class FileFormat:
>     name: str
>     path: pathlib.Path
>     entry_point: int
>     checksec: dict[str, bool]
>     sections: list[FileFormatSection]
>
>     def __init__(self, path: str | pathlib.Path) -> None:
>         raise NotImplementedError
>
>     def __init_subclass__(cls: Type["FileFormat"], **kwargs):
>         global __registered_file_formats__
>         super().__init_subclass__(**kwargs)
>         required_attributes = ("name", "entry_point", "is_valid",
> "checksec",)
>         for attr in required_attributes:
>             if not hasattr(cls, attr):
>                 raise NotImplementedError(f"File format '{cls.__name__}'
> is invalid: missing attribute '{attr}'")
>         __registered_file_formats__.add(cls)
>         return
>
>     @classmethod
>     def is_valid(cls, _: pathlib.Path) -> bool:
>         raise NotImplementedError
>
>     def __str__(self) -> str:
>         return f"{self.name}('{self.path.absolute()}', entry @
> {self.entry_point:#x})"
>
>
> class Elf(FileFormat):
>     """Basic ELF parsing.
>     Ref:
>     - http://www.skyfree.org/linux/references/ELF_Format.pdf
>     - https://refspecs.linuxfoundation.org/elf/elfspec_ppc.pdf
>     - https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html
>     """
>     class Class(enum.Enum):
>         ELF_32_BITS       = 0x01
>         ELF_64_BITS       = 0x02
>
>     ELF_MAGIC         = 0x7f454c46
>
>     class Abi(enum.Enum):
>         X86_64            = 0x3e
>         X86_32            = 0x03
>         ARM               = 0x28
>         MIPS              = 0x08
>         POWERPC           = 0x14
>         POWERPC64         = 0x15
>         SPARC             = 0x02
>         SPARC64           = 0x2b
>         AARCH64           = 0xb7
>         RISCV             = 0xf3
>         IA64              = 0x32
>         M68K              = 0x04
>
>     class Type(enum.Enum):
>         ET_RELOC          = 1
>         ET_EXEC           = 2
>         ET_DYN            = 3
>         ET_CORE           = 4
>
>     class OsAbi(enum.Enum):
>         SYSTEMV     = 0x00
>         HPUX        = 0x01
>         NETBSD      = 0x02
>         LINUX       = 0x03
>         SOLARIS     = 0x06
>         AIX         = 0x07
>         IRIX        = 0x08
>         FREEBSD     = 0x09
>         OPENBSD     = 0x0C
>
>     e_magic: int                = ELF_MAGIC
>     e_class: "Elf.Class"        = Class.ELF_32_BITS
>     e_endianness: Endianness    = Endianness.LITTLE_ENDIAN
>     e_eiversion: int
>     e_osabi: "Elf.OsAbi"
>     e_abiversion: int
>     e_pad: bytes
>     e_type: "Elf.Type"          = Type.ET_EXEC
>     e_machine: Abi              = Abi.X86_32
>     e_version: int
>     e_entry: int
>     e_phoff: int
>     e_shoff: int
>     e_flags: int
>     e_ehsize: int
>     e_phentsize: int
>     e_phnum: int
>     e_shentsize: int
>     e_shnum: int
>     e_shstrndx: int
>
>     path: pathlib.Path
>     phdrs : list["Phdr"]
>     shdrs : list["Shdr"]
>     name: str = "ELF"
>
>     __checksec : dict[str, bool]
>
>     def __init__(self, path: str | pathlib.Path) -> None:
>         """Instantiate an ELF object. A valid ELF must be provided, or an
> exception will be thrown."""
>
>         if isinstance(path, str):
>             self.path = pathlib.Path(path).expanduser()
>         elif isinstance(path, pathlib.Path):
>             self.path = path
>         else:
>             raise TypeError
>
>         if not self.path.exists():
>             raise FileNotFoundError(f"'{self.path}' not found/readable,
> most gef features will not work")
>
>         self.__checksec = {}
>
>         with self.path.open("rb") as self.fd:
>             # off 0x0
>             self.e_magic, e_class, e_endianness, self.e_eiversion =
> self.read_and_unpack(">IBBB")
>             if self.e_magic != Elf.ELF_MAGIC:
>                 # The ELF is corrupted, GDB won't handle it, no point
> going further
>                 raise RuntimeError("Not a valid ELF file (magic)")
>
>             self.e_class, self.e_endianness = Elf.Class(e_class),
> Endianness(e_endianness)
>
>             if self.e_endianness != gef.arch.endianness:
>                 warn("Unexpected endianness for architecture")
>
>             endian = self.e_endianness
>
>             # off 0x7
>             e_osabi, self.e_abiversion =
> self.read_and_unpack(f"{endian}BB")
>             self.e_osabi = Elf.OsAbi(e_osabi)
>
>             # off 0x9
>             self.e_pad = self.read(7)
>
>             # off 0x10
>             e_type, e_machine, self.e_version =
> self.read_and_unpack(f"{endian}HHI")
>             self.e_type, self.e_machine = Elf.Type(e_type),
> Elf.Abi(e_machine)
>
>             # off 0x18
>             if self.e_class == Elf.Class.ELF_64_BITS:
>                 self.e_entry, self.e_phoff, self.e_shoff =
> self.read_and_unpack(f"{endian}QQQ")
>             else:
>                 self.e_entry, self.e_phoff, self.e_shoff =
> self.read_and_unpack(f"{endian}III")
>
>             self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum =
> self.read_and_unpack(f"{endian}IHHH")
>             self.e_shentsize, self.e_shnum, self.e_shstrndx =
> self.read_and_unpack(f"{endian}HHH")
>
>             self.phdrs = []
>             for i in range(self.e_phnum):
>                 self.phdrs.append(Phdr(self, self.e_phoff +
> self.e_phentsize * i))
>
>             self.shdrs = []
>             for i in range(self.e_shnum):
>                 self.shdrs.append(Shdr(self, self.e_shoff +
> self.e_shentsize * i))
>         return
>
>     def read(self, size: int) -> bytes:
>         return self.fd.read(size)
>
>     def read_and_unpack(self, fmt: str) -> tuple[Any, ...]:
>         size = struct.calcsize(fmt)
>         data = self.fd.read(size)
>         return struct.unpack(fmt, data)
>
>     def seek(self, off: int) -> None:
>         self.fd.seek(off, 0)
>
>     def __str__(self) -> str:
>         return f"ELF('{self.path.absolute()}', {self.e_class.name}, {
> self.e_machine.name})"
>
>     def __repr__(self) -> str:
>         return f"ELF('{self.path.absolute()}', {self.e_class.name}, {
> self.e_machine.name})"
>
>     @property
>     def entry_point(self) -> int:
>         return self.e_entry
>
>     @classmethod
>     def is_valid(cls, path: pathlib.Path) -> bool:
>         return u32(path.open("rb").read(4), e = Endianness.BIG_ENDIAN) ==
> Elf.ELF_MAGIC
>
>     @property
>     def checksec(self) -> dict[str, bool]:
>         """Check the security property of the ELF binary. The following
> properties are:
>         - Canary
>         - NX
>         - PIE
>         - Fortify
>         - Partial/Full RelRO.
>         Return a dict() with the different keys mentioned above, and the
> boolean
>         associated whether the protection was found."""
>         if not self.__checksec:
>             def __check_security_property(opt: str, filename: str,
> pattern: str) -> bool:
>                 cmd   = [readelf,]
>                 cmd  += opt.split()
>                 cmd  += [filename,]
>                 lines = gef_execute_external(cmd, as_list=True)
>                 for line in lines:
>                     if re.search(pattern, line):
>                         return True
>                 return False
>
>             abspath = str(self.path.absolute())
>             readelf = gef.session.constants["readelf"]
>             self.__checksec["Canary"] = __check_security_property("-rs",
> abspath, r"__stack_chk_fail") is True
>             has_gnu_stack = __check_security_property("-W -l", abspath,
> r"GNU_STACK") is True
>             if has_gnu_stack:
>                 self.__checksec["NX"] = __check_security_property("-W -l",
> abspath, r"GNU_STACK.*RWE") is False
>             else:
>                 self.__checksec["NX"] = False
>             self.__checksec["PIE"] = __check_security_property("-h",
> abspath, r":.*EXEC") is False
>             self.__checksec["Fortify"] = __check_security_property("-s",
> abspath, r"_chk@GLIBC") is True
>             self.__checksec["Partial RelRO"] =
> __check_security_property("-l", abspath, r"GNU_RELRO") is True
>             self.__checksec["Full RelRO"] = self.__checksec["Partial
> RelRO"] and __check_security_property("-d", abspath, r"BIND_NOW") is True
>         return self.__checksec
>
>     @classproperty
>     @deprecated("use `Elf.Abi.X86_64`")
>     def X86_64(cls) -> int: return Elf.Abi.X86_64.value # pylint:
> disable=no-self-argument
>
>     @classproperty
>     @deprecated("use `Elf.Abi.X86_32`")
>     def X86_32(cls) -> int : return Elf.Abi.X86_32.value # pylint:
> disable=no-self-argument
>
>     @classproperty
>     @deprecated("use `Elf.Abi.ARM`")
>     def ARM(cls) -> int : return Elf.Abi.ARM.value # pylint:
> disable=no-self-argument
>
>     @classproperty
>     @deprecated("use `Elf.Abi.MIPS`")
>     def MIPS(cls) -> int : return Elf.Abi.MIPS.value # pylint:
> disable=no-self-argument
>
>     @classproperty
>     @deprecated("use `Elf.Abi.POWERPC`")
>     def POWERPC(cls) -> int : return Elf.Abi.POWERPC.value # pylint:
> disable=no-self-argument
>
>     @classproperty
>     @deprecated("use `Elf.Abi.POWERPC64`")
>     def POWERPC64(cls) -> int : return Elf.Abi.POWERPC64.value # pylint:
> disable=no-self-argument
>
>     @classproperty
>     @deprecated("use `Elf.Abi.SPARC`")
>     def SPARC(cls) -> int : return Elf.Abi.SPARC.value # pylint:
> disable=no-self-argument
>
>     @classproperty
>     @deprecated("use `Elf.Abi.SPARC64`")
>     def SPARC64(cls) -> int : return Elf.Abi.SPARC64.value # pylint:
> disable=no-self-argument
>
>     @classproperty
>     @deprecated("use `Elf.Abi.AARCH64`")
>     def AARCH64(cls) -> int : return Elf.Abi.AARCH64.value  # pylint:
> disable=no-self-argument
>
>     @classproperty
>     @deprecated("use `Elf.Abi.RISCV`")
>     def RISCV(cls) -> int : return Elf.Abi.RISCV.value # pylint:
> disable=no-self-argument
>
>

[-- Attachment #2: Type: text/html, Size: 19416 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* bug#74738: 31.0.50; Freezes in Python-mode on some Python file when searching or scrolling
  2024-12-08 17:37 ` Eli Zaretskii
  2024-12-09 14:58   ` rehan malak
@ 2024-12-09 15:04   ` kobarity
  1 sibling, 0 replies; 5+ messages in thread
From: kobarity @ 2024-12-09 15:04 UTC (permalink / raw)
  To: rehan malak, Eli Zaretskii; +Cc: 74738, Stefan Monnier


rehan malak wrote:
> Please tell me if there is a workaround without losing python syntax color ?

If you can configure Emacs to use tree-sitter, python-ts-mode would
not be affected by this problem.

Eli Zaretskii wrote:
> 
> > From: rehan malak <rehan.malak@gmail.com>
> > Date: Sun, 8 Dec 2024 14:05:36 +0100
> > 
> > I can reproduce systematically this freeze dealing with a 10000lines
> > Python file :
> > 
> > wget https://raw.githubusercontent.com/hugsy/gef/refs/heads/main/gef.py
> > emacs -Q gef.py

It seems to be reproducible after the following commit:

commit 3b3274a85c2f5df21d76d82e0d7740005aa84fdf
Author: Stefan Monnier <monnier@iro.umontreal.ca>
Date:   Fri Oct 16 14:03:59 2020 -0400

    * lisp/progmodes/python.el: Teach f-strings to `font-lock`

    (python--f-string-p, python--font-lock-f-strings): New functions.
    (python-font-lock-keywords-maximum-decoration): Use them.

> A much smaller reproducer is attached below.  Just visiting it freezes
> Emacs.  But if I change the line marked below:
> 
>     @classmethod
>     def is_valid(cls, _: pathlib.Path) -> bool:
>         raise NotImplementedError
> 
>     def __str__(self) -> str:
>   >>>>> return f"{self.name}('{self.path.absolute()}', entry @ {self.entry_point:#x})"
> 
> to say this instead:
> 
>         return self.name
> 
> the problem goes away.  So it is something in that complex expression
> that trips syntax-ppss.
> 
> Stefan and kobarity, any suggestions or ideas?

I could not reproduce the freeze, but it took several seconds to visit
the reproducer.  It appears to be possible to work around this by
simply removing the format specifier ":#x".

return f"{self.name}('{self.path.absolute()}', entry @ {self.entry_point})"

So there may be a problem with font-lock when there is an f-string
format specifier, but I have not found the root cause yet.





^ permalink raw reply	[flat|nested] 5+ messages in thread

* bug#74738: 31.0.50; Freezes in Python-mode on some Python file when searching or scrolling
  2024-12-09 14:58   ` rehan malak
@ 2024-12-11 14:24     ` kobarity
  0 siblings, 0 replies; 5+ messages in thread
From: kobarity @ 2024-12-11 14:24 UTC (permalink / raw)
  To: rehan malak, Eli Zaretskii; +Cc: 74738, Stefan Monnier

[-- Attachment #1: Type: text/plain, Size: 1682 bytes --]

rehan malak wrote:
> Deleting all the "#" in the f-string of the gef.py file removes the problem on my side.
>  sed -i -e 's/#0{align/0{align/g' gef.py
>  sed -i -e 's/#0{width/0{width/g' gef.py
>  sed -i -e 's/#07x}/07x}/g' gef.py
>  sed -i -e 's/#06x}/06x}/g' gef.py
>  sed -i -e 's/#04x}/04x}/g' gef.py
>  sed -i -e 's/#4x}/4x}/g' gef.py
>  sed -i -e 's/#8x}/8x}/g' gef.py
>  sed -i -e 's/#10x}/10x}/g' gef.py
>  sed -i -e 's/#x}/x}/g' gef.py
> 
> Your smaller example Eli also contains a f-string with the "#".
> 
> This python f-string format specifier is described here:
> https://docs.python.org/3/library/string.html#format-specification-mini-language
> 
> Is this format specifier supported from the beginning ?
> 
> For example :
> 
> value = 0xab
> print(f"{value:x} is a value")
> 
> => color syntax : is a value    has the color defined by font-lock-string-face
> 
> while
> 
> value = 0xab
> print(f"{value:#x} is a value")
> 
> => color are messed up... is has color defined by font-lock-keyword-face and a value has color defined by default face

The mechanism of the hang is still unclear, but I found the cause of
the font-lock malfunction: when using `up-list' to find the
corresponding closing brace for an opening brace in an f-string, if
the format specifier contains "#", the rest of the string is
considered a comment and the search fails.  Therefore, it can be
worked around by temporarily binding `parse-sexp-ignore-comments' to
nil, as in the attached patch.

Hangs on gef.py can also be avoided with this patch, but it assumes
that the braces are properly closed.  If you remove the closing braces
of expressions containing "#", it will eventually hang.

[-- Attachment #2: 0001-Fix-font-lock-of-Python-f-strings.patch --]
[-- Type: text/plain, Size: 1999 bytes --]

From 697f942d70e40bcd9bd32596313a9a371fffe35c Mon Sep 17 00:00:00 2001
From: kobarity <kobarity@gmail.com>
Date: Wed, 11 Dec 2024 23:21:04 +0900
Subject: [PATCH] Fix font-lock of Python f-strings

* lisp/progmodes/python.el (python--font-lock-f-strings): Bind
'parse-sexp-ignore-comments' to nil so that we can look for
closing braces even if a hash is used in the format specifier.
* test/lisp/progmodes/python-tests.el
(python-font-lock-f-string-1): New test.  (Bug#74738)
---
 lisp/progmodes/python.el            | 4 +++-
 test/lisp/progmodes/python-tests.el | 8 ++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index cfa3cc59568..827f437013f 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -631,7 +631,9 @@ python--font-lock-f-strings
               (forward-char 1)          ;Just skip over {{
             (let ((beg (match-beginning 0))
                   (end (condition-case nil
-                           (progn (up-list 1) (min send (point)))
+                           (let ((parse-sexp-ignore-comments))
+                             (up-list 1)
+                             (min send (point)))
                          (scan-error send))))
               (goto-char end)
               (put-text-property beg end 'face nil))))
diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el
index 4cc4040d0ff..ac17c5b1263 100644
--- a/test/lisp/progmodes/python-tests.el
+++ b/test/lisp/progmodes/python-tests.el
@@ -709,6 +709,14 @@ python-font-lock-string-literal-concatenation
      (17 . font-lock-operator-face) (18)
      (19 . font-lock-string-face))))
 
+(ert-deftest python-font-lock-f-string-1 ()
+  "Test for bug#74738."
+  (python-tests-assert-faces
+   "print(f\"{value:#x} is a value\")"
+   '((1 . font-lock-builtin-face) (6)
+     (8 . font-lock-string-face) (9)
+     (19 . font-lock-string-face) (31))))
+
 \f
 ;;; Indentation
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2024-12-11 14:24 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-08 13:05 bug#74738: 31.0.50; Freezes in Python-mode on some Python file when searching or scrolling rehan malak
2024-12-08 17:37 ` Eli Zaretskii
2024-12-09 14:58   ` rehan malak
2024-12-11 14:24     ` kobarity
2024-12-09 15:04   ` kobarity

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).