Skip to content

Fix: is_fully_escaped does not handle consecutive backslashes correctly#14393

Merged
bluetech merged 9 commits intopytest-dev:mainfrom
EternalRights:fix-is-fully-escaped-consecutive-backslashes
Apr 25, 2026
Merged

Fix: is_fully_escaped does not handle consecutive backslashes correctly#14393
bluetech merged 9 commits intopytest-dev:mainfrom
EternalRights:fix-is-fully-escaped-consecutive-backslashes

Conversation

@EternalRights
Copy link
Copy Markdown
Contributor

Closes #14392

Description

The is_fully_escaped function in raises.py only checked whether the character immediately before a regex metacharacter was a backslash. It did not count how many consecutive backslashes preceded the metacharacter.

In regex, backslashes escape each other in pairs. So \\. (two backslashes then a dot) means an escaped backslash followed by an unescaped dot. But is_fully_escaped(r\\.) was incorrectly returning True because it only checked if s[i-1] was \.

This affects pytest.raises(match=...) error messages: when the match pattern contains escaped backslashes before a metacharacter, the function incorrectly considers the pattern fully escaped and skips showing a regex diff on match failure.

Fix: Count consecutive backslashes before each metacharacter. Even count means the metacharacter is not escaped; odd count means it is.

Type of Change

  • Bug fix

Checklist

  • Added tests for changed functionality
  • Added changelog entry
  • No documentation update needed (internal helper function)

The function only checked if the character immediately before a regex
metacharacter was a backslash, but did not count how many consecutive
backslashes preceded it. When two backslashes appear before a
metacharacter (e.g. r'\\.'), the first escapes the second, leaving the
metacharacter unescaped. The function incorrectly reported such strings
as fully escaped.

Fix by counting consecutive backslashes: an even count means the
metacharacter is not escaped, an odd count means it is.

Closes pytest-dev#14392
@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Apr 18, 2026
EternalRights and others added 3 commits April 18, 2026 21:31
r'\\\\|' is 4 backslashes + pipe. Even count means pipe is not
escaped, so is_fully_escaped should return False, not True.
4 backslashes + pipe: even count means pipe is not escaped, so is_fully_escaped should return False, not True.
Copy link
Copy Markdown

@alokshukla631 alokshukla631 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Counting the run of preceding backslashes matches regex escaping rules much better than the old single-character check, and the added cases make the intended behavior easy to follow.

@EternalRights
Copy link
Copy Markdown
Contributor Author

@alokshukla631
Thank you for your review and feedback!
I've updated the PR branch to stay in sync with the main branch, and I'm now awaiting approval from a maintainer with write access.
If you have any further suggestions, feel free to let me know.

Comment thread changelog/14392.bugfix.rst Outdated
Comment thread src/_pytest/raises.py Outdated
return not any(
c in metacharacters and (i == 0 or s[i - 1] != "\\") for (i, c) in enumerate(s)
)
for i, c in enumerate(s):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a regex for it instead? I'm thinking we can do it in 2 steps:

  1. Strip all escapes (re.sub(r'\\.', '', pattern))
  2. Check if still contains any metacharacters (simple containment check).

Step 1 should ensure that none of the chars found in step 2 are escaped.

I think that would be shorter and a bit more obvious.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! Updated the changelog and refactored is_fully_escaped to use the regex approach — it's cleaner this way.

Copy link
Copy Markdown
Member

@bluetech bluetech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@bluetech bluetech added the backport 9.0.x apply to PRs at any point; backports the changes to the 9.0.x branch label Apr 25, 2026
@bluetech bluetech enabled auto-merge (squash) April 25, 2026 18:53
@bluetech bluetech merged commit a38b20d into pytest-dev:main Apr 25, 2026
33 checks passed
@patchback
Copy link
Copy Markdown

patchback Bot commented Apr 25, 2026

Backport to 9.0.x: 💔 cherry-picking failed — conflicts found

❌ Failed to cleanly apply a38b20d on top of patchback/backports/9.0.x/a38b20d79039d50c504320b5d4d790c61893a90e/pr-14393

Backporting merged PR #14393 into main

  1. Ensure you have a local repo clone of your fork. Unless you cloned it
    from the upstream, this would be your origin remote.
  2. Make sure you have an upstream repo added as a remote too. In these
    instructions you'll refer to it by the name upstream. If you don't
    have it, here's how you can add it:
    $ git remote add upstream https://github.com/pytest-dev/pytest.git
  3. Ensure you have the latest copy of upstream and prepare a branch
    that will hold the backported code:
    $ git fetch upstream
    $ git checkout -b patchback/backports/9.0.x/a38b20d79039d50c504320b5d4d790c61893a90e/pr-14393 upstream/9.0.x
  4. Now, cherry-pick PR Fix: is_fully_escaped does not handle consecutive backslashes correctly #14393 contents into that branch:
    $ git cherry-pick -x a38b20d79039d50c504320b5d4d790c61893a90e
    If it'll yell at you with something like fatal: Commit a38b20d79039d50c504320b5d4d790c61893a90e is a merge but no -m option was given., add -m 1 as follows instead:
    $ git cherry-pick -m1 -x a38b20d79039d50c504320b5d4d790c61893a90e
  5. At this point, you'll probably encounter some merge conflicts. You must
    resolve them in to preserve the patch from PR Fix: is_fully_escaped does not handle consecutive backslashes correctly #14393 as close to the
    original as possible.
  6. Push this branch to your fork on GitHub:
    $ git push origin patchback/backports/9.0.x/a38b20d79039d50c504320b5d4d790c61893a90e/pr-14393
  7. Create a PR, ensure that the CI is green. If it's not — update it so that
    the tests and any other checks pass. This is it!
    Now relax and wait for the maintainers to process your pull request
    when they have some cycles to do reviews. Don't worry — they'll tell you if
    any improvements are necessary when the time comes!

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport 9.0.x apply to PRs at any point; backports the changes to the 9.0.x branch bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix: is_fully_escaped does not handle consecutive backslashes correctly

3 participants