Skip to content
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

Variable update does not update it's javaType and then it causes TypeError in org.jruby.embed.jsr223.Utils#postEval #1952

Closed
guai opened this issue Sep 5, 2014 · 12 comments

Comments

@guai
Copy link

guai commented Sep 5, 2014

This bug I got only when used ScriptEngine.
I updated global variable inside eval. It held java object. New value is java object too.
I think jruby synchronizes global vars after eval in org.jruby.embed.jsr223.Utils#postEval.
And here container.getVarMap().get(key) causes TypeError because javaType field of this var still holds the first value's class.
I found out that update goes through org.jruby.embed.variable.AbstractVariable#updateRubyObject and it does not update javaType.
So I patched it like this:

    protected void updateRubyObject(IRubyObject rubyObject) {
        if (rubyObject == null) {
            return;
        }
        this.irubyObject = rubyObject;
        if(rubyObject instanceof ConcreteJavaProxy)
            javaType = rubyObject.getJavaClass();
        // delays updating javaObject for performance.
    }
@guai
Copy link
Author

guai commented Sep 5, 2014

jruby 1.7.13

@guai
Copy link
Author

guai commented Sep 5, 2014

I also hit another bug in ScriptEngine.
Global var with new value here org.jruby.embed.jsr223.Utils#preEval got it's old value back. In this code:

        Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
        for (Map.Entry<String, Object> entry : bindings.entrySet()) {
            Utils.put(container, receiver, entry.getKey(), entry.getValue(), context);
        }

@guai
Copy link
Author

guai commented Oct 6, 2014

Can be reproduced like this:
-J-Dorg.jruby.embed.localvariable.behavior=persistent

manager = Java::javax.script.ScriptEngineManager.new
e = manager.getEngineByName("jruby")
b = Java::javax.script.SimpleBindings.new
e.eval 'a = 123', b  
e.eval 'a = "fff"', b  
puts b['a']

@guai
Copy link
Author

guai commented Nov 14, 2014

Also hit case when global var gets it's old value back in this lines of GlobalVariable.java (commented it to fix)

    public void setJavaObject(Ruby runtime, Object javaObject) {
        updateByJavaObject(runtime, javaObject);
//        tryEagerInjection(runtime, null); //!!!
    }

Code that set this global var (which new value was lost) even was not call from inside ScriptEngine. It was called not far before ScriptEngine using.

@mslinn
Copy link

mslinn commented Dec 2, 2017

Please fix so that JRuby becomes usable as an embedded scripting language. I have a test suite that shows how this bug affects programmers. The test suite also shows equivalent JSR223 code for Nashorn, Groovy, and Jython.

@headius
Copy link
Member

headius commented Jan 25, 2018

@mslinn Sometimes bugs just slip through the cracks. We're certainly willing to fix things that are affecting users today. I'm not sure how to run your examples, but it appears the behavior you expect is similar to the PERSISTENT variable mode that @guai uses above. Perhaps we just need to set our JSR223 implementation to use that mode?

You may be able to test that change yourself, but in any case could you please open another bug showing how we can run these examples?

@guai Regarding the original bug report...

On JRuby master this appears to now set javaType to null, which then forces it to be re-retrieved when accessing the variable later. I believe that fixes the original issue, but without a runnable test case I can't be sure. The commit for that change was 37234e1.

The second example, where a global variable apparently retains a previous value, does not reproduce in 9.1.13, the original version reported (edit: oops, original reported version was 1.7.13 and I mixed them up in my mind. The examples given all appear to work in JRuby 9.1.15). If it remains an issue in JRuby 9.1.15 (or upcoming 9.1.16) please file a new issue.

@headius headius closed this as completed Jan 25, 2018
@mslinn
Copy link

mslinn commented Jan 25, 2018

You can just run the unit tests that I referenced to see the bug

@headius
Copy link
Member

headius commented Jan 25, 2018

@mslinn Please open a separate bug for the issues you found and we'll tidy them up!

@headius headius modified the milestones: Won't Fix, JRuby 9.1.13.0 Jan 25, 2018
@headius
Copy link
Member

headius commented Jan 25, 2018

@mslinn Setting the property @guai mentioned does indeed appear to make all your expected cases run successfully. So the debate then, which we can have in a separate issue, is whether our behavior is actually incorrect.

Ruby typically does not reuse the root scope for a script execution, and variables defined at that level are not visible across scripts. However, this case is more along the lines of what we'd call a "scriptlet" and so defaulting to persistent variables may be valid.

I am reluctant to change it without some discussion, since there are likely other JRuby+JSR-223 users out there who may depend on the current behavior.

Here's the test that runs successfully on JRuby 9.1.15.0:

  "JRubyEvaluator" should {
    "work" in {
      System.setProperty("org.jruby.embed.localvariable.behavior", "persistent")
      val jruby = new JRubyEvaluator(useClassloader = false)

      jruby.scriptEngineOk shouldBe true
      val engineFactories: List[ScriptEngineFactory] = jruby.scriptEngineFactories
      engineFactories.size should be > 0

      jruby.showEngineFactories(engineFactories)
      jruby.scriptEngine should not be null
      jruby.scriptEngine.getFactory.getLanguageName shouldBe "ruby"

      jruby.isDefined("ten")   shouldBe false
      jruby.put("ten", 10)     shouldBe 10
      jruby.get("ten")         shouldBe 10
      jruby.isDefined("ten")   shouldBe true

      jruby.put("twenty", 20)  shouldBe 20
      jruby.get("twenty")      shouldBe 20

      // Seems to define a new variable, but that variable is not added to bindings.
      // The new value is returned fine, however
      val Some(twelve) = jruby.eval("twelve = 12")
      twelve                   shouldBe 12
      jruby.get("twelve")      shouldBe 12 // Not bound, so fails

      jruby.eval("twelve = ten + 2")  // NameError: undefined local variable or method `ten' for main:Object
//      Did you mean?  test
//        <main> at <script>:1

      jruby.get("twelve")      shouldBe 12  // Fails, unlike Nashorn and Jython JSR223 implementations

      jruby.eval("twelve")     shouldBe Some(12) // twelve is not bound
      jruby.eval("twelve * 2") shouldBe Some(24)
      jruby.get("twelve")      shouldBe 12
    }

@mslinn
Copy link

mslinn commented Jan 25, 2018

@headius Yes, setting the environment variable solves the problem. I did that in the constructor for the JRuby evaluator. All my tests pass.

class JRubyEvaluator(useClassloader: Boolean = true) extends JSR223Evaluator[JRubyEvaluator]("ruby", useClassloader) {
  // See https://github.com/jruby/jruby/issues/1952#issuecomment-360546825
  System.setProperty("org.jruby.embed.localvariable.behavior", "persistent")
}

@headius
Copy link
Member

headius commented Jan 26, 2018

👍 Since there's not really a bug per se I'll open an issue to discuss switching the default for JSR223 mode.

@headius
Copy link
Member

headius commented Feb 26, 2018

FYI, JRuby 9.2+ will default to "persistent" for the 223 engine. You can set it back to "global" if necessary using the property discussed here, org.jruby.embed.localvariable.behavior=global.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants