{"record":{"id":"208fdccfcb657e15","repo":"elastic/elasticsearch","slug":"invalid-base-cell-looking-for-neighbor","errorCode":null,"errorMessage":"Invalid base cell looking for neighbor","messagePattern":"Invalid base cell looking for neighbor","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"libs/h3/src/main/java/org/elasticsearch/h3/HexRing.java","lineNumber":680,"sourceCode":"    /**\n     * Returns the hexagon index neighboring the origin, in the direction dir.\n     *\n     * Implementation note: The only reachable case where this returns -1 is if the\n     * origin is a pentagon and the translation is in the k direction. Thus,\n     * -1 can only be returned if origin is a pentagon.\n     *\n     * @param origin Origin index\n     * @param dir Direction to move in\n     * @return H3Index of the specified neighbor or -1 if there is no more neighbor\n     */\n    static long h3NeighborInDirection(long origin, int dir) {\n        long current = origin;\n\n        int newRotations = 0;\n        int oldBaseCell = H3Index.H3_get_base_cell(current);\n        if (oldBaseCell < 0 || oldBaseCell >= Constants.NUM_BASE_CELLS) {  // LCOV_EXCL_BR_LINE\n            // Base cells less than zero can not be represented in an index\n            throw new IllegalArgumentException(\"Invalid base cell looking for neighbor\");\n        }\n        int oldLeadingDigit = H3Index.h3LeadingNonZeroDigit(current);\n\n        // Adjust the indexing digits and, if needed, the base cell.\n        int r = H3Index.H3_get_resolution(current) - 1;\n        while (true) {\n            if (r == -1) {\n                current = H3Index.H3_set_base_cell(current, baseCellNeighbors[oldBaseCell][dir]);\n                newRotations = baseCellNeighbor60CCWRots[oldBaseCell][dir];\n\n                if (H3Index.H3_get_base_cell(current) == INVALID_BASE_CELL) {\n                    // Adjust for the deleted k vertex at the base cell level.\n                    // This edge actually borders a different neighbor.\n                    current = H3Index.H3_set_base_cell(current, baseCellNeighbors[oldBaseCell][CoordIJK.Direction.IK_AXES_DIGIT.digit()]);\n                    newRotations = baseCellNeighbor60CCWRots[oldBaseCell][CoordIJK.Direction.IK_AXES_DIGIT.digit()];\n\n                    // perform the adjustment for the k-subsequence we're skipping\n                    // over.","sourceCodeStart":662,"sourceCodeEnd":698,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/h3/src/main/java/org/elasticsearch/h3/HexRing.java#L662-L698","documentation":"Thrown by HexRing.h3NeighborInDirection when the base-cell field of the origin index is outside [0, NUM_BASE_CELLS). h3NeighborInDirection uses the base cell to index baseCellNeighbors/baseCellNeighbor60CCWRots, so an out-of-range base cell would cause an ArrayIndexOutOfBoundsException; this guard throws a clear error instead. The method is package-private and reached via hexRingPosToH3, child/noChild pos methods, and areNeighbours' fallback.","triggerScenarios":"An operation that walks neighbors (hexRing, hexRingPosToH3, noChildIntersectingPosToH3, areNeighbours fallback) is given an origin long whose base-cell nibble is invalid. With public API input this is masked by earlier validity checks; it surfaces when a corrupt or non-H3 long is passed in.","commonSituations":"Passing an opaque long from an external/untrusted source into a neighbor walk without validation; deserialization/endian corruption of an H3 long; mixing indexes from incompatible H3 versions whose base-cell layout differs.","solutions":["Validate with H3.h3IsValid(h3) before any neighbor/ring/child operation.","Re-derive indexes from lat/lng via geoToH3 rather than trusting external longs.","If you must accept raw longs, treat h3IsValid == false as a hard error at the trust boundary."],"exampleFix":"// before\nlong[] ring = H3.hexRing(rawLong); // throws Invalid base cell if base-cell field invalid\n\n// after\nif (H3.h3IsValid(rawLong) == false) {\n    throw new IllegalArgumentException(\"not a valid H3 index: \" + rawLong);\n}\nlong[] ring = H3.hexRing(rawLong);","handlingStrategy":"validation","validationCode":"static long[] safeRing(long h3) {\n    if (!org.elasticsearch.h3.H3.h3IsValid(h3)) {\n        throw new IllegalArgumentException(\"not a valid H3 index: \" + h3);\n    }\n    return org.elasticsearch.h3.H3.hexRing(h3);\n}","typeGuard":"static boolean isUsable(long h3) {\n    return org.elasticsearch.h3.H3.h3IsValid(h3);\n}","tryCatchPattern":"try {\n    return H3.hexRing(h3);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().equals(\"Invalid base cell looking for neighbor\")) {\n        throw new IllegalArgumentException(\"corrupt or non-H3 index: \" + h3, e);\n    }\n    throw e;\n}","preventionTips":["Validate every external long with h3IsValid before any neighbor/ring/child op.","Re-derive indexes from lat/lng via geoToH3.","Reject corrupt longs at the trust boundary rather than deep in a neighbor walk."],"tags":["h3","neighbor","base-cell","validation"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}