Bug #14353

$SAFE should stay at least thread-local for compatibility

Added by Eregon (Benoit Daloze) 2 months ago. Updated 2 months ago.

Target version:


In #14250 $SAFE changed from a frame+thread-local variable to a process-wide global variable.

This feels wrong and breaks the most common usage of $SAFE in tests: {
  $SAFE = 1
  sth that should be checked to work under $SAFE==1

It is very clear this is incompatible given how many files (33!) had to be changed in r61510.
And it has wide ranging confusing side-effects, one example:

I agree frame-local is too much for $SAFE.

But removing thread-local seems to only introduce large incompatibilities.

It also makes it impossible to use it in a thread-safe way.
The common pattern (not necessarily for $SAFE, more often for $VERBOSE):

  old = $SAFE
  $SAFE = 1
  something under SAFE==1
  $SAFE = old

is unsafe if two threads run it concurrently (The last thread executing $SAFE = old might restore to 1 even though it should be 0).

(Actually I believe most built-in variables (e.g. $VERBOSE) should be thread-local and not process-wide due to this)

Since $SAFE is being deprecated and removed, I don't see any reason to make it more incompatible than needed.

ko1 (Koichi Sasada) Can we switch it back to thread-local for compatibility, avoiding headaches and keeping it usable with multiple threads?

Related issues

Related to Ruby trunk - Feature #14250: Make `$SAFE` process global state and allow to set 0 againClosed


#1 [ruby-core:84851] Updated by Eregon (Benoit Daloze) 2 months ago

  • Assignee set to ko1 (Koichi Sasada)

#2 Updated by Eregon (Benoit Daloze) 2 months ago

  • Target version set to 2.6

#3 Updated by Eregon (Benoit Daloze) 2 months ago

  • Related to Feature #14250: Make `$SAFE` process global state and allow to set 0 again added

#4 [ruby-core:84852] Updated by shevegen (Robert A. Heiler) 2 months ago

I have seen $VERBOSE used in ways very similar as you described though and have used
it myself; most commonly I used it e. g. when I would use syck for yaml files rather
than psych, in order to suppress warnings I'd get during loading. It never felt very
elegant though to use the re-assignment trick. Can't think of a more elegant
solution either.

#5 [ruby-core:84886] Updated by ko1 (Koichi Sasada) 2 months ago

  • Assignee changed from ko1 (Koichi Sasada) to matz (Yukihiro Matsumoto)

Of course, I proposed $SAFE as Fiber local.

However, Matz said process global is enough.
akr also said nobody use $SAFE correctly, so that such incompatibility is not a matter.

I'm neutral. Matz, please decide it.

#6 [ruby-core:84892] Updated by Eregon (Benoit Daloze) 2 months ago

Of course, I proposed $SAFE as Fiber local.


If $SAFE had no effect at all, then I think it would be fine to make $SAFE a normal process-wide global variable.
But this is not the case, $SAFE still causes SecurityError.
Therefore any library/code setting $SAFE will potentially break other threads, which is quite incompatible.

I think a more compatible way to remove $SAFE is:
* Warn that $SAFE is deprecated.
* Make it Fiber-local or Thread-local. (because very few people expect it frame-local and it incurs a cost)
* Warn that $SAFE is has no longer any effect (or maybe even raise when $SAFE is set, like for levels 2-4) and remove $SAFE checks, so the value of $SAFE has no effect.
* Remove the variable entirely or let it be process global.

Something like

ruby -e '{ $SAFE=1 }.join;{ load "test.rb".taint }.join'

fails, prints no warning and could happen in any big application if $SAFE is set to 1 in some Thread.
This doesn't seem worth breaking. Even more so without a warning.

Also available in: Atom PDF