-
Notifications
You must be signed in to change notification settings - Fork 195
Throw a runtime error if #binding is called from a C extensions module (non-ruby frame) #3436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
0c3f184 to
cd89254
Compare
C ext module (non-ruby frame) Co-authored-by: Randy Stauner <[email protected]>
cd89254 to
3c3d1be
Compare
| value = "getAdoptedNode(this).getEncapsulatingSourceSection()", | ||
| allowUncached = true, neverDefault = false) SourceSection sourceSection) { | ||
| final InternalMethod method = RubyArguments.tryGetMethod(callerFrame); | ||
| if (method == null || method.getDeclaringModule().toString().equals("Truffle::CExt")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference it seems tryGetMethod is looking up the ruby method that is the context of the frame calling binding.
So in the case of the test
https://github.com/oracle/truffleruby/blob/0c3f184c762ed0b5cce42d5b33f8c1b8bcb46658/spec/ruby/optional/capi/binding_spec.rb#L14
is backed by
https://github.com/oracle/truffleruby/blob/0c3f184c762ed0b5cce42d5b33f8c1b8bcb46658/spec/ruby/optional/capi/ext/binding_spec.c#L8-L9
and the method of the caller frame then points to CExt#rb_funcallv
https://github.com/oracle/truffleruby/blob/0c3f184c762ed0b5cce42d5b33f8c1b8bcb46658/lib/truffle/truffle/cext.rb#L1059
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure in what scenario tryGetMethod might return null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's probably also a better check here than doing a string comparison on the method's module, but we hadn't found anything.
Any pointers on what else we might be able to use for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is org.truffleruby.core.CoreLibrary#truffleCExtModule and then it can be compared with ==.
But comparing on the module is hacky, because it means every Truffle::CExt method can no longer access binding.
Which seems a problem notably for:
https://github.com/oracle/truffleruby/blob/610cea3302a7302c5ec3efd1a6f89b26467d04c3/lib/truffle/truffle/cext.rb#L2039-L2041
That can be worked around, but it shows it's rather hacky.
I'm not sure what's a good solution for detecting C ext methods, maybe/probably we should define Truffle::CExt#rb_define_method in Java eventually. Or find another way to mark those.
But I think for now a more useful check would be to check if sourceSection == CoreLibrary.JAVA_CORE_SOURCE_SECTION, which means a method defined in Java.
Getting a binding from there will always be empty, which seems not useful, so that seems the corresponding case to CRuby having methods defined in C and for which no meaningful binding can be given.
An example test for that is:
$ ruby -e 'p method(:binding).call'
-e:1:in `binding': Cannot create Binding object for non-Ruby caller (RuntimeError)
Currently on TruffleRuby it's:
$ ruby -e 'p method(:binding).call'
#<Binding:0xc8>
$ ruby -e 'p method(:binding).call.local_variables'
[]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW for the existing spec in spec/ruby/optional/capi/binding_spec.rb I would think we already raise an error for that case, because the callerFrame should be null in that case since it is a call from LLVM/Sulong.
But maybe there is something in between which means there is a frame somehow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the master branch this spec fails with
CApiBindingSpecs Kernel#binding raises when called from C FAILED
Expected RuntimeError but no exception was raised (#<Binding:0x368> was returned)
I loaded a debugger to check the sourceSection and it is not the same as CoreLibrary.JAVA_CORE_SOURCE_SECTION.
If I use a different ruby script that calls binding from ruby code the sourceSection here doesn't have any different properties from the above (other than the path) that I can see 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, so the SourceSection there is https://github.com/oracle/truffleruby/blob/677ac08b296b086a7d6ede73f6cbebe7c8027a6a/lib/truffle/truffle/cext.rb#L1060
So let's not try to pass this specific spec for now, because it would require changing rb_funcallv which is out of scope.
But instead let's add another spec, using method(:binding).call, and that should raise the RuntimeError, and that one we can use the check for JAVA_CORE_SOURCE_SECTION.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing worth trying to pass that C API spec would be to change SendWithoutCExtLockBaseNode to call callWithFrameAndBlock without a frame (so with null instead). But that may have other side effects which might be undesirable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| allowUncached = true, neverDefault = false) SourceSection sourceSection) { | ||
| final InternalMethod method = RubyArguments.tryGetMethod(callerFrame); | ||
| if (method == null || method.getDeclaringModule().toString().equals("Truffle::CExt")) { | ||
| throw new RaiseException(getContext(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you use an InlinedBranchProfile errorProfile here, so this branch is only explored by the JIT if it ever happened? (that way it can also optimize better because it knows there is no throw)

Part of #3039
Fixes
[easy, java] Kernel#binding raises RuntimeError if called from a non-Ruby frame (such as a method defined in C). [[Bug #18487](https://bugs.ruby-lang.org/issues/18487)]Following what was done in Cruby 3.2 PR here
Raises an error if the frame that is called from is not Ruby.
Co-authored with @rwstauner