smartparens icon indicating copy to clipboard operation
smartparens copied to clipboard

Coding issue: code transformation commands should use the '*' in the interactive spec stating they do not modify read-only buffers.

Open pierre-rouleau opened this issue 4 years ago • 12 comments
trafficstars

Expected code behavior

The interactive functions that transform code, such as sp-add-to-previous-sexp should use the "*" character in the interactive form to prevent operation inside a read-only buffer. So instead of using:

(interactive "P")

it should use:

(interactive "*P")

I could provide a PR if I see a reply here.

Version information

smartparens version: 20210904.1621

pierre-rouleau avatar Oct 01 '21 19:10 pierre-rouleau

With or without the * I get the same error Buffer is read-only: #<buffer *scratch8*>.

What version of Emacs are you using. I can't see the difference between the two versions of the interaction spec. For "read only" I used read-only-mode. How did you setup the buffer?

Fuco1 avatar Nov 01 '21 11:11 Fuco1

With or without the * I get the same error Buffer is read-only: #<buffer *scratch8*>.

What version of Emacs are you using. I can't see the difference between the two versions of the interaction spec. For "read only" I used read-only-mode. How did you setup the buffer?

Fuco1 avatar Nov 01 '21 11:11 Fuco1

I was just reading the code. I tried setting a buffer with read-only-mode and applied sp-add-to-previous-sexp and it did refuse to execute with the error stating the buffer was read-only as expected.

I reported that because I normally always put a * in the interactive spec of code that modifies a buffer as the docstring of interactive states:

"... In addition, if the string begins with ‘*’, an error is signaled if the buffer is read-only."

I assume something else detects the buffer being read only. What part of you code detects it?

I'm using Emacs 26.3

pierre-rouleau avatar Nov 01 '21 11:11 pierre-rouleau

No, my code does not detect it. That error comes from Emacs.

Fuco1 avatar Nov 01 '21 16:11 Fuco1

This specification is probably for functions which should not be run in read-only buffers, even though they don't modify the buffer.

Attempting to modify a read-only buffer results in an error anyway, which is what happens here.

ThibautVerron avatar Nov 29 '21 07:11 ThibautVerron

The code in emacs/src/callint.c that defines the behaviour checks buffer state when '*' is specified and returns an error if the buffer is read-only.

IMO using the '*' explicitly is a good practice. It would always be possible for a function to modify a global variable before attempting to modify the read-only buffer. In that case the '*" interactive specification will prevent the function to execute. If you do not have it the function will run, will modify the variable and then the code trying to modify the buffer will raise the error, potentially leaving the data in an invalid state.

If that code was mine I would add the '*'. It's not my code.

pierre-rouleau avatar Nov 29 '21 13:11 pierre-rouleau

Yes I agree. I'd even go further and say that functions which shouldn't run in read-only buffers, regardless of whether they modify the buffer, should take steps to check if the buffer is read only themselves.

The interactive specification "*" is not necessarily enough, as it won't catch non-interactive calls.

But I don't think any of smartparens' buffer modification functions has such hidden side effects.

ThibautVerron avatar Nov 29 '21 13:11 ThibautVerron

It's possible that the current code has no side effect. But a language feature like that is a kind of defensive-programming mechanism. It does not hurt not having it in place (except for extra C code lines executed) unless someone wants to and it will protect against future modifications. It also has a documentation value.

But again, this is not my code. If the author wants to close the ticket instead, that's fine to me.

pierre-rouleau avatar Nov 29 '21 13:11 pierre-rouleau

@ThibautVerron , the reason non-interactive functions do not have checks for read-only buffers might be performance. However a large chunck of code is called via interactive functions. If all these interactive function take advantage of the '*' specification that makes the function explicitly check for the buffer being read-only then the probability of preventing execution of non-interactive code that should check for buffer being read-only and does not is reduced.

I agree with you that non-interactive function that modifies variables and modifies a buffer should check for buffer being read-only before proceeding.

pierre-rouleau avatar Nov 29 '21 13:11 pierre-rouleau

@ Fuco1, I modified the ticket title to properly describe what I reported. I am advocating using this defensive-programming utility that prevents interactive function to operate on a read-only buffer. I would do it but if you prefer leaving your code as is then it's your call.

pierre-rouleau avatar Nov 29 '21 14:11 pierre-rouleau

@ThibautVerron , the reason non-interactive functions do not have checks for read-only buffers might be performance. However a large chunck of code is called via interactive functions. If all these interactive function take advantage of the '*' specification that makes the function explicitly check for the buffer being read-only then the probability of preventing execution of non-interactive code that should check for buffer being read-only and does not is reduced.

I agree with you that non-interactive function that modifies variables and modifies a buffer should check for buffer being read-only before proceeding.

I didn't mean (only) adding non-interactive functions. An interactive function can still be called non-interactively, in which case the interactive form will not be evaluated.

For a (stupid) example, I could write a function like this:

(defun my-function ()
  (interactive)
  (sp-delete-sexp)
  (sp-forward-sexp))

and if I call this function interactively, the interactive spec of sp-delete-sexp will never be evaluated.

There are two errors there of course, I should add the star to the interactive spec, and/or I should use call-interactively. But those are not mistakes you would usually expect to bite you.

Afaik the only solution for those cases is to identify those dangerous functions, document the danger and have a hard check. It's not an argument for or against using the interactive specification everywhere, of course.

ThibautVerron avatar Nov 29 '21 14:11 ThibautVerron

@ThibautVerron You are correct: an interactive spec is only checked for the function called interactively, either from the user action or from a call-interactively call, and not from a direct call. It would be nice this last case was handled, but it's not. From what I understand of the implementation that's because it's the interactive and call-interactive code that does the check.

Emacs Lisp is a dynamic language and that's one of its protection mechanisms. It's not protecting in 100% of the use cases, but it's useful.

I use all of these protection mechanism as much as I can. I know it's not enough. For dynamic languages a good unit test coverage also helps.

The problem is often not when the code is written but later when maintenance, refactoring or plain changes are done by the original author or someone else. Anything (including valid, up-to-date docs) that reduces probability of error in the long term is a good thing.

pierre-rouleau avatar Nov 29 '21 15:11 pierre-rouleau

I added the * spec to commands which modify the buffer content.

Fuco1 avatar Jan 18 '23 13:01 Fuco1

Thanks for adding it.

pierre-rouleau avatar Jan 18 '23 14:01 pierre-rouleau