I am submitting a change to JNA which has in previous releases defined a set of constants as int
type, specifically:
int VER_EQUAL = 1;
int VER_GREATER = 2;
int VER_GREATER_EQUAL = 3;
... etc...
(Since they are defined in an interface
they are automatically static
and final
.)
These constants are used as the Condition
argument in the VerSetConditionMask function, which requires a BYTE
argument, mapped in JNA to Java's byte
.
To use the existing int
constants in that function would require me (and other users) to explicitly typecast them, so I want to change these constant values in the library to be byte
, e.g.,
byte VER_EQUAL = 1;
byte VER_GREATER = 2;
byte VER_GREATER_EQUAL = 3;
... etc...
I do not think this change breaks backwards compatibility, because any code using these constants must currently expect at least an int
, and will happily widen the byte
without complaint. I've written plenty of test code to convince myself of this. I'm aware that changing Object
types (like Integer
to Byte
) would not work, but I believe this is fine for primitives. However, despite an hour searching the web for confirmation, I remain the tiniest bit unconvinced.
Is this change backwards compatible? Or am I wrong and is there a case where this could break existing code relying on the int
primitive?
Java occasionally breaks backward compatibility, both at the source and at the binary level, when doing so is judged to be more beneficial than not doing so, including in Java 17, which removes RMI Activation (and begins the removal process of the Security Manager).
Java 9 introduces a modular system which is moving away from today's monolithic Java SE platform. Backward compatibility is one of the main priorities and the Oracle engineering team has worked on a smooth transition to Java 9.
Backward compatible (also known as downward compatible or backward compatibility) refers to a hardware or software system that can successfully use interfaces and data from earlier versions of the system or with other systems.
Maybe it's obvious. But maybe not. If some third party user fetched the int
values and stored them locally in Integer
objects, changing the int
to byte
will cause a compile time error there:
interface IntToByteWithInt
{
int VER_EQUAL = 1;
int VER_GREATER = 2;
int VER_GREATER_EQUAL = 3;
}
interface IntToByteWithByte
{
byte VER_EQUAL = 1;
byte VER_GREATER = 2;
byte VER_GREATER_EQUAL = 3;
}
public class IntToByte
{
public static void main(String[] args)
{
Integer a = IntToByteWithInt.VER_EQUAL;
// Type mismatch: cannot convert from byte to Integer
Integer b = IntToByteWithByte.VER_EQUAL;
}
}
Beyond that, I think that one should at least mention reflection for completeness.
I also considered the ternary operator (https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.25.2 ) as a hot candidate, but did not manage to cause an error with that one.
Is this change backwards compatible?
No.
Even though constants are normally inlined, the field is still a part of the class file, so it is possible that some dynamic lookup references the field using it's old type which is int
. For instance:
// defined in `MyClass`
static final byte x = 10;
public static void main(String[] args) throws Throwable {
lookup().findStaticGetter(MyClass.class, "x", int.class); // old code
}
This throws a NoSuchFieldException
since the lookup is looking for the old type of the field.
This also goes for byte code writing APIs that access the field, e.g. with ASM:
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(55, ACC_PUBLIC, "Test", null, "java/lang/Object", null);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()V", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitFieldInsn(GETSTATIC, "MyClass", "x", "I"); // looking for int field
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
Class<?> cls = lookup().defineClass(cw.toByteArray());
cls.getMethod("m").invoke(null); // NoSuchFieldError
The above code works and prints 10
if the field is of type int
, but fails once it is changed to byte
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With