Project

General

Profile

Actions

Bug #22324

closed

Pure attributes allow Clang to remove observable C API effects

Bug #22324: Pure attributes allow Clang to remove observable C API effects

Added by yqtian (Yongqiang Tian) 4 days ago. Updated 2 days ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 4.1.0dev (2026-09-16T01:40:57Z :detached: e60ce574f0) +PRISM [x86_64-linux]
[ruby-core:126742]

Description

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:

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:

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:

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:

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?

Actions

Also available in: PDF Atom