From: "yqtian (Yongqiang Tian) via ruby-core" Date: 2026-09-16T09:11:20+00:00 Subject: [ruby-core:126742] [Ruby Bug#22324] Pure attributes allow Clang to remove observable C API effects Issue #22324 has been reported by yqtian (Yongqiang Tian). ---------------------------------------- Bug #22324: Pure attributes allow Clang to remove observable C API effects https://bugs.ruby-lang.org/issues/22324 * Author: yqtian (Yongqiang Tian) * Status: Open * ruby -v: ruby 4.1.0dev (2026-09-16T01:40:57Z :detached: e60ce574f0) +PRISM [x86_64-linux] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Several public C APIs are marked `RBIMPL_ATTR_PURE()` even though their documented behavior includes observable effects. Clang can consequently remove calls when an extension discards the return value. I reproduced three cases against current Ruby at e60ce574f0: 1. `rb_intern_const()` creates and permanently registers a symbol. When the return value is discarded, Clang removes the call and the symbol does not appear in `Symbol.all_symbols`. 2. `rb_class_inherited_p()` can raise the documented `TypeError`. When the return value is discarded, Clang removes the call and execution continues. 3. `rb_class_superclass()` can raise `TypeError` for an uninitialized class. That discarded call is also removed. The relevant extension functions are: ```c static VALUE probe_intern_discard(VALUE self, VALUE name) { rb_intern_const(StringValueCStr(name)); return Qnil; } static VALUE probe_inherited_discard(VALUE self) { rb_class_inherited_p(rb_cObject, Qnil); return ID2SYM(rb_intern("continued")); } static VALUE probe_superclass_discard(VALUE self) { VALUE uninitialized_class = rb_obj_alloc(rb_cClass); rb_class_superclass(uninitialized_class); return ID2SYM(rb_intern("continued")); } ``` The test uses a fresh generated symbol name and checks `Symbol.all_symbols` before and after the first call. It also catches `TypeError` around the two class calls. With the current headers and Clang 18, I observed: ```text intern_before=false intern_after=false expected_after=true inherited_discard=continued expected=type_error superclass_discard=continued expected=type_error ``` These removals occur even in the tested `-O0` extension build because the header explicitly tells the compiler that the calls are pure. A possible fix is to remove `RBIMPL_ATTR_PURE()` from: ```text rb_intern_const() rb_class_inherited_p() rb_class_superclass() ``` I did not remove the separate `noalias` annotation from `rb_intern_const()`, because it is not needed to reproduce this problem. After removing only the three `pure` attributes, the same extension reports: ```text intern_before=false intern_after=true expected_after=true inherited_discard=type_error expected=type_error superclass_discard=type_error expected=type_error ``` The isolated change leaves the independent typed-data null probe unchanged, and the complete patched Ruby tree passes `make test`. Should these symbol-registration and documented exception effects be preserved when callers discard the return values? If so, is removing `pure` from these three declarations the preferred correction, or is there a narrower contract Ruby would prefer for any of them? -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/