Skip to content

static final fields are cached in jpy, sometimes incorrectly. #191

Description

@niloc132

When a Java class is built into a Python JType, all static+final variables are read and stored in the __dict__

jpy/src/main/c/jpy_jtype.c

Lines 1467 to 1472 in a8c43da

if (isStatic && isFinal) {
// Add static final values to the JPy_JType's tp_dict.
// todo: Note that this is a workaround only, because the JPy_JType's tp_getattro slot is not called.
if (JType_AddFieldAttribute(jenv, declaringClass, fieldKey, fieldType, fid) < 0) {
return -1;
}

Non-static fields each have an instance field built for them, for when instances are interacted with:

jpy/src/main/c/jpy_jtype.c

Lines 1473 to 1485 in a8c43da

} else if (!isStatic) {
// Add instance field accessor to the JPy_JType's tp_dict, this will be evaluated in the JPy_JType's tp_setattro and tp_getattro slots.
field = JField_New(declaringClass, fieldKey, fieldType, isStatic, isFinal, fid);
if (field == NULL) {
JPy_DIAG_PRINT(JPy_DIAG_F_TYPE + JPy_DIAG_F_ERR, "JType_ProcessField: WARNING: Java field '%s' rejected because an error occurred during field instantiation\n", fieldName);
return -1;
}
if (JType_AcceptField(declaringClass, field)) {
JType_AddField(declaringClass, field);
} else {
JField_Del(field);
}

But non-final static fields are ignored:

jpy/src/main/c/jpy_jtype.c

Lines 1486 to 1488 in a8c43da

} else {
JPy_DIAG_PRINT(JPy_DIAG_F_TYPE + JPy_DIAG_F_ERR, "JType_ProcessField: WARNING: Java field '%s' rejected because is is static, but not final\n", fieldName);
}


Unfortunately, final fields are not always immutable - in the JVM there are several ways to modify final fields:

  • Reflection API (through Field.setAccessible())
  • java.lang.invoke (through Lookup.unreflect() since there's no way to get a setter for a final field)
  • JNI (SetXXXField())
  • sun.misc.Unsafe (setXXX()/setXXXUnaligned())

One easy example here is System.out - while this is final, the class also has System.setOut(PrintStream), allowing runtime code to replace the out instance (presumably implemented in JNI, I haven't checked). Invoking System.setOut() has no impact on the System.out value, from the perspective of Python via jpy.

The post at https://shipilev.net/jvm/anatomy-quarks/17-trust-nonstatic-final-fields/ has more good discussion on this - the latter shows some heuristics that the JVM uses when "trusting" final fields, but the answer is mostly "don't trust them".


Instead of assigning members on the dict for fields, we probably should offer an implementation for tp_getset or some other way to let the field be read/written programmatically? I might be interpreting this wrong.

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