Skip to content

Latest msgpack (1.4.3) broken on JRuby when using Java 8 VM #239

Description

@ivoanjo

Hello! I work on Datadog's ddtrace gem and noticed this issue on our CI today.

The latest msgpack version, when run with the Java 8 VM breaks:

$ docker run jruby:9.3-jre8 bash -c "gem install msgpack; ruby -rmsgpack -e 'puts RUBY_DESCRIPTION; [1,2,3].to_msgpack'"
/opt/jruby/lib/ruby/stdlib/socket.rb:4: warning: already initialized constant Socket::Constants::AF_INET6
/opt/jruby/lib/ruby/stdlib/socket.rb:4: warning: already initialized constant Socket::Constants::IPPROTO_ICMPV6
/opt/jruby/lib/ruby/stdlib/socket.rb:4: warning: already initialized constant Socket::Constants::IPPROTO_IPV6
/opt/jruby/lib/ruby/stdlib/socket.rb:4: warning: already initialized constant Socket::Constants::PF_INET6
/opt/jruby/lib/ruby/stdlib/socket.rb:4: warning: already initialized constant Socket::Constants::SOL_IPV6
Successfully installed msgpack-1.4.3-java
1 gem installed
jruby 9.3.3.0 (2.6.8) 2022-01-19 b26de1f5c5 OpenJDK 64-Bit Server VM 25.312-b07 on 1.8.0_312-b07 +jit [linux-x86_64]
Unhandled Java exception: java.lang.NoSuchMethodError: java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;
java.lang.NoSuchMethodError: java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;
           readRubyString at org/msgpack/jruby/Encoder.java:70
                   encode at org/msgpack/jruby/Encoder.java:81
                    write at org/msgpack/jruby/Packer.java:128
                     call at org/msgpack/jruby/Packer$INVOKER$i$1$0$write.gen:-1
             cacheAndCall at org/jruby/runtime/callsite/CachingCallSite.java:372
                     call at org/jruby/runtime/callsite/CachingCallSite.java:175
              processCall at org/jruby/ir/interpreter/InterpreterEngine.java:316

Note that I've used JRuby 9.3 above, but it also happens on 9.2, since the issue is with the version of the Java VM:

$ docker run jruby:9.2-jre8 bash -c "gem install msgpack; ruby -rmsgpack -e 'puts RUBY_DESCRIPTION; [1,2,3].to_msgpack'"
Successfully installed msgpack-1.4.3-java
1 gem installed
jruby 9.2.20.1 (2.5.8) 2021-11-30 2a2962fbd1 OpenJDK 64-Bit Server VM 25.312-b07 on 1.8.0_312-b07 +jit [linux-x86_64]
Unhandled Java exception: java.lang.NoSuchMethodError: java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;
java.lang.NoSuchMethodError: java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;
           readRubyString at org/msgpack/jruby/Encoder.java:70
                   encode at org/msgpack/jruby/Encoder.java:81
                    write at org/msgpack/jruby/Packer.java:128
                     call at org/msgpack/jruby/Packer$INVOKER$i$1$0$write.gen:-1
             cacheAndCall at org/jruby/runtime/callsite/CachingCallSite.java:376
                     call at org/jruby/runtime/callsite/CachingCallSite.java:175
              processCall at org/jruby/ir/interpreter/InterpreterEngine.java:316

This test works correctly if instead of the jruby:9.2-jre8 and jruby:9.3-jre8 we switch to jruby:9.2-jre11 and jruby:9.3-jre11.

My analysis of the issue is as follows:

In Encoder.java the code calls buffer.clear():

The buffer here refers to a java.nio.ByteBuffer. In Java 8, ByteBuffer::clear is actually inherited from its parent class:

Screen Shot 2022-01-20 at 12 25 37

Looking at its parent class, the signature for clear is:

Screen Shot 2022-01-20 at 12 27 21

and thus in ByteBuffer the signature for calling this method using Java bytecode is

java.nio.ByteBuffer.clear()Ljava/nio/Buffer;

...whereas msgpack seems to be looking for a

java.nio.ByteBuffer.clear()Ljava/nio/ByteBuffer;

The latter method was actually added in Java 9:

Screen Shot 2022-01-20 at 12 29 30

Notice how it says that it overrides the method clear, and the return is now ByteBuffer, not just Buffer. (Having a subclass override a return with a more specific type is allowed in Java).

Now it is true that msgpack is compiled with source and target version 1.8 (aka Java 8):

msgpack-ruby/Rakefile

Lines 37 to 38 in 1af1773

ext.source_version = '1.8'
ext.target_version = '1.8'

But I'm betting that the release was not compiled on Java 8, and that we're running into this known limitation (from here):

If you do not specify the correct version of bootstrap classes, the compiler will use the old language rules (in this example, it will use version 1.6 of the Java programming language) combined with the new bootstrap classes, which can result in class files that do not work on the older platform (in this case, Java SE 6) because reference to non-existent methods can get included.

TL;DR it's not enough to use source and target as Java 1.8, you actually need to be running Java 8 OR feed the compiler with a Java 8 rt.jar file.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions