Skip to content

JRI: use attribute-name length in rniGetAttrNames#351

Open
r-suzuki wants to merge 1 commit intos-u:masterfrom
r-suzuki:fix-jri-attrnames-length
Open

JRI: use attribute-name length in rniGetAttrNames#351
r-suzuki wants to merge 1 commit intos-u:masterfrom
r-suzuki:fix-jri-attrnames-length

Conversation

@r-suzuki
Copy link
Copy Markdown

@r-suzuki r-suzuki commented May 6, 2026

Thank you for maintaining rJava/JRI.

This PR proposes a small fix for what appears to be an issue in rniGetAttrNames on R 4.6.0.

In the R 4.6.0 code path, the attribute names are obtained with R_getAttribNames(o) and stored in ans, but the Java array size is currently taken from XLENGTH(o).

Since the length of the target object is independent of the number of attributes, zero-length objects with attributes can return null.

This change uses XLENGTH(ans) for the Java array size, matching the later loop over ans.

- ac = (unsigned int) XLENGTH(o);
+ ac = (unsigned int) XLENGTH(ans);

The minimal failing case is:

structure(numeric(0), foo = 1)

This object has length 0, but it has one attribute name, foo.

Test program

Save as rtest_attrnames.java and run with:

./run rtest_attrnames --vanilla
import org.rosuda.JRI.Rengine;
import java.util.Arrays;

public class rtest_attrnames {
    private static void check(Rengine re, String expr, String[] expected) {
        long xp = re.rniEval(re.rniParse(expr, 1), 0);
        String[] got = re.rniGetAttrNames(xp);

        System.out.println(expr);
        System.out.println("got      = " + Arrays.toString(got));
        System.out.println("expected = " + Arrays.toString(expected));

        if (!Arrays.equals(got, expected)) {
            throw new RuntimeException("unexpected attribute names for: " + expr);
        }
    }
    
    public static void main(String[] args) {
        if (!Rengine.versionCheck()) {
            throw new RuntimeException("JRI version mismatch");
        }

        Rengine re = new Rengine(new String[] {"--vanilla"}, false, null);

        if (!re.waitForR()) {
            throw new RuntimeException("cannot load R");
        }

        try {
            check(re,
                "structure(numeric(0), foo = 1)",
                new String[] {"foo"});

            check(re,
                "structure(1, foo = 1, bar = 2)",
                new String[] {"foo", "bar"});

            check(re,
                "structure(1:10, foo = 1)",
                new String[] {"foo"});

            check(re,
                "1:10",
                null);
        } finally {
            re.end();
        }
    }
}

Result with R 4.5.0

The test succeeds.

structure(numeric(0), foo = 1)
got      = [foo]
expected = [foo]
structure(1, foo = 1, bar = 2)
got      = [foo, bar]
expected = [foo, bar]
structure(1:10, foo = 1)
got      = [foo]
expected = [foo]
1:10
got      = null
expected = null

Result with R 4.6.0 before this change

The first case fails, and the program terminates with a RuntimeException.

structure(numeric(0), foo = 1)
got      = null
expected = [foo]
Exception in thread "main" java.lang.RuntimeException: unexpected attribute names for: structure(numeric(0), foo = 1)
        at rtest_attrnames.check(rtest_attrnames.java:14)
        at rtest_attrnames.main(rtest_attrnames.java:30)

Result with R 4.6.0 after this change

The test succeeds.

structure(numeric(0), foo = 1)
got      = [foo]
expected = [foo]
structure(1, foo = 1, bar = 2)
got      = [foo, bar]
expected = [foo, bar]
structure(1:10, foo = 1)
got      = [foo]
expected = [foo]
1:10
got      = null
expected = null

Thank you for taking a look. Please let me know if I missed anything.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant