Welcome! Please see the About page for a little more info on how this works.

0 votes
ago in Java Interop by

AIMesh provides several overloaded create methods. There is one with an int parameter and another one with a long parameter. They have very different semantics.

This code snippet intends to call the one with the long parameter:

(defn read-model
  "read a 3D model from a file"
  [path]
  (let [scene (Assimp/aiImportFile path (bit-or Assimp/aiProcess_Triangulate
                                                Assimp/aiProcess_FlipUVs))]
    (if (= scene nil)
      (println (Assimp/aiGetErrorString))
      (for [index (range (.mNumMeshes scene))]
        (let [pointer (.get (.mMeshes scene) index)]
          (assert (= (type pointer) java.lang.Long))
          (AIMesh/create pointer))))))

The assertion right before the problematic call makes sure that pointer is indeed a long; but nevertheless the int overload of create is called, as can be verified in the stack trace:

{:clojure.main/message
 "Execution error (IllegalArgumentException) at java.nio.Buffer/createCapacityException (Buffer.java:290).\ncapacity < 0: (-689151616 < 0)\n",
 :clojure.main/triage
 {:clojure.error/class java.lang.IllegalArgumentException,
  :clojure.error/line 290,
  :clojure.error/cause "capacity < 0: (-689151616 < 0)",
  :clojure.error/symbol java.nio.Buffer/createCapacityException,
  :clojure.error/source "Buffer.java",
  :clojure.error/phase :execution},
 :clojure.main/trace
 {:via
  [{:type clojure.lang.Compiler$CompilerException,
    :message
    "Syntax error macroexpanding at (learnopengl/core.clj:12:1).",
    :data
    {:clojure.error/phase :execution,
     :clojure.error/line 12,
     :clojure.error/column 1,
     :clojure.error/source "learnopengl/core.clj"},
    :at [clojure.lang.Compiler load "Compiler.java" 7665]}
   {:type java.lang.IllegalArgumentException,
    :message "capacity < 0: (-689151616 < 0)",
    :at [java.nio.Buffer createCapacityException "Buffer.java" 290]}],
  :trace
  [[java.nio.Buffer createCapacityException "Buffer.java" 290]
   [java.nio.Buffer <init> "Buffer.java" 253]
   [java.nio.ByteBuffer <init> "ByteBuffer.java" 316]
   [java.nio.ByteBuffer <init> "ByteBuffer.java" 324]
   [java.nio.MappedByteBuffer <init> "MappedByteBuffer.java" 113]
   [java.nio.DirectByteBuffer <init> "DirectByteBuffer.java" 107]
   [java.nio.ByteBuffer allocateDirect "ByteBuffer.java" 360]
   [org.lwjgl.system.Struct __create "Struct.java" 118]
   [org.lwjgl.assimp.AIMesh create "AIMesh.java" 487]
   [jdk.internal.reflect.DirectMethodHandleAccessor
    invoke
    "DirectMethodHandleAccessor.java"
    103]
   [java.lang.reflect.Method invoke "Method.java" 580]
   [clojure.lang.Reflector invokeMatchingMethod "Reflector.java" 167]
   [clojure.lang.Reflector invokeStaticMethod "Reflector.java" 332]
   [learnopengl.mesh_model$read_model$iter__332__336$fn__337$fn__338
    invoke
    "mesh_model.clj"
    66]
   [learnopengl.mesh_model$read_model$iter__332__336$fn__337
    invoke
    "mesh_model.clj"
    63]
   [clojure.lang.LazySeq sval "LazySeq.java" 42]
   [clojure.lang.LazySeq seq "LazySeq.java" 51]
   [clojure.lang.RT seq "RT.java" 535]
   [clojure.lang.RT countFrom "RT.java" 650]
   [clojure.lang.RT count "RT.java" 643]
   [learnopengl.core$eval351 invokeStatic "core.clj" 12]
   [learnopengl.core$eval351 invoke "core.clj" 12]
   [clojure.lang.Compiler eval "Compiler.java" 7194]
   [clojure.lang.Compiler load "Compiler.java" 7653]
   [clojure.lang.RT loadResourceScript "RT.java" 381]
   [clojure.lang.RT loadResourceScript "RT.java" 372]
   [clojure.lang.RT load "RT.java" 459]
   [clojure.lang.RT load "RT.java" 424]
   [clojure.core$load$fn__6908 invoke "core.clj" 6161]
   [clojure.core$load invokeStatic "core.clj" 6160]
   [clojure.core$load doInvoke "core.clj" 6144]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [clojure.core$load_one invokeStatic "core.clj" 5933]
   [clojure.core$load_one invoke "core.clj" 5928]
   [clojure.core$load_lib$fn__6850 invoke "core.clj" 5975]
   [clojure.core$load_lib invokeStatic "core.clj" 5974]
   [clojure.core$load_lib doInvoke "core.clj" 5953]
   [clojure.lang.RestFn applyTo "RestFn.java" 142]
   [clojure.core$apply invokeStatic "core.clj" 669]
   [clojure.core$load_libs invokeStatic "core.clj" 6016]
   [clojure.core$load_libs doInvoke "core.clj" 6000]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.core$apply invokeStatic "core.clj" 669]
   [clojure.core$require invokeStatic "core.clj" 6038]
   [clojure.core$require doInvoke "core.clj" 6038]
   [clojure.lang.RestFn invoke "RestFn.java" 408]
   [user$eval140$fn__144 invoke "form-init15974239925031016013.clj" 1]
   [user$eval140 invokeStatic "form-init15974239925031016013.clj" 1]
   [user$eval140 invoke "form-init15974239925031016013.clj" 1]
   [clojure.lang.Compiler eval "Compiler.java" 7194]
   [clojure.lang.Compiler eval "Compiler.java" 7184]
   [clojure.lang.Compiler load "Compiler.java" 7653]
   [clojure.lang.Compiler loadFile "Compiler.java" 7591]
   [clojure.main$load_script invokeStatic "main.clj" 475]
   [clojure.main$init_opt invokeStatic "main.clj" 477]
   [clojure.main$init_opt invoke "main.clj" 477]
   [clojure.main$initialize invokeStatic "main.clj" 508]
   [clojure.main$null_opt invokeStatic "main.clj" 542]
   [clojure.main$null_opt invoke "main.clj" 539]
   [clojure.main$main invokeStatic "main.clj" 664]
   [clojure.main$main doInvoke "main.clj" 616]
   [clojure.lang.RestFn applyTo "RestFn.java" 137]
   [clojure.lang.Var applyTo "Var.java" 705]
   [clojure.main main "main.java" 40]],
  :cause "capacity < 0: (-689151616 < 0)",
  :phase :execution}}

This repository contains the full code to reproduce the error. read-model is defined in mesh-model.clj.

I ran this with Clojure version 1.11.1 and Java version:

openjdk 21.0.7 2025-04-15
OpenJDK Runtime Environment (Red_Hat-21.0.7.0.6-2) (build 21.0.7+6)
OpenJDK 64-Bit Server VM (Red_Hat-21.0.7.0.6-2) (build 21.0.7+6, mixed mode, sharing)

There was a previous question on stackoverflow leading to this question.

1 Answer

+1 vote
ago by
selected ago by
 
Best answer

This is a known issue with reflection (it's actually not entirely deterministic - both methods match and either may get chosen depending on the JVM and other factors).

The important takeaway is that you are leaving the choice to reflection rather than making it explicit. If you use (set! *warn-on-reflection* true) you should see a warning here.

You can use either a type hint (AIMesh/create ^long pointer) or an explicit coercion (AIMesh/create (long pointer)) or param-tags metadata (^[long] AIMesh/create pointer) to remove the ambiguity. You should see the warning go away if it's working.

ago by
Note param-tags are new in Clojure 1.12, so that one won't work in Clojure 1.11.
ago by
I put `(set! *warn-on-reflection* true)` at the top of the body of `read-model`, but I did not see a warning.
ago by
But setting a type hint did the trick. Thank you.
ago by
Do I need to worry about reflection only with Java interop? Or also in pure Clojure?
...