From 27209f25c2b32608ed2df7acea68b0af8d0767f0 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 7 Aug 2026 10:44:40 +0200 Subject: [PATCH 1/6] GiwsException: release the JNI local references on the early-return paths The retrieve helpers returned early (NULL result or pending secondary exception) without releasing their local references. As giws embeds the JVM, local references are only freed when the thread detaches, so they accumulated. Also store the inline GetObjectClass(javaException) result so it can be released on every path. --- CHANGELOG | 3 +++ CXXException.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 04d291c..a868bef 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,9 @@ giws (3.1.0) * Generated GiwsException: check for pending exceptions after each CallObjectMethod (fixes -Xcheck:jni warnings while building the C++ exception) + * Generated GiwsException: release the JNI local references on the + early-return paths too (they leaked when embedding the JVM, as + local references are only freed when the thread detaches) * Add a test suite (tests/) run by CI; no JDK needed * New example bug_nonstatic_exception demonstrating the exception fix on non-static methods diff --git a/CXXException.py b/CXXException.py index bb387d9..57a6208 100644 --- a/CXXException.py +++ b/CXXException.py @@ -403,7 +403,8 @@ def generateCXXBody(self, config): // retrieve information from the exception. // get method id - jmethodID getLocalizedMessageId = curEnv->GetMethodID(curEnv->GetObjectClass(javaException), + jclass javaExceptionClass = curEnv->GetObjectClass(javaException); + jmethodID getLocalizedMessageId = curEnv->GetMethodID(javaExceptionClass, "getLocalizedMessage", "()Ljava/lang/String;"); @@ -413,11 +414,13 @@ def generateCXXBody(self, config): if (curEnv->ExceptionCheck()) { curEnv->ExceptionClear(); + curEnv->DeleteLocalRef(javaExceptionClass); return ""; } if (description == NULL) { + curEnv->DeleteLocalRef(javaExceptionClass); return ""; } @@ -425,6 +428,7 @@ def generateCXXBody(self, config): // release java resources curEnv->DeleteLocalRef(description); + curEnv->DeleteLocalRef(javaExceptionClass); return res; } @@ -441,9 +445,11 @@ def generateCXXBody(self, config): // retrieve information from the exception. // get method id // getStackTrace returns an array of StackTraceElement - jmethodID getStackTraceId = curEnv->GetMethodID(curEnv->GetObjectClass(javaException), + jclass javaExceptionClass = curEnv->GetObjectClass(javaException); + jmethodID getStackTraceId = curEnv->GetMethodID(javaExceptionClass, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); + curEnv->DeleteLocalRef(javaExceptionClass); // call getStackTrace jobjectArray stackTrace = (jobjectArray) curEnv->CallObjectMethod(javaException, getStackTraceId); @@ -536,6 +542,8 @@ def generateCXXBody(self, config): if (javaName == NULL) { + curEnv->DeleteLocalRef(exceptionClass); + curEnv->DeleteLocalRef(classClass); return ""; } From d3f1029701190549552377de7f8ae69618674b58 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 7 Aug 2026 10:44:49 +0200 Subject: [PATCH 2/6] boolean arrays: fix an uninitialized pointer write with --disable-return-size-array booleanDataGiws emitted 'int *lenRow;' followed by '*lenRow = GetArrayLength(res)': a write through an uninitialized pointer. Align it with the base class dataGiws which declares a local 'int lenRow' value when the size is not returned through an output parameter. --- CHANGELOG | 3 +++ datatypes/booleanDataGiws.py | 35 ++++++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a868bef..b3d6a60 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,9 @@ giws (3.1.0) * Generated GiwsException: release the JNI local references on the early-return paths too (they leaked when embedding the JVM, as local references are only freed when the thread detaches) + * Generated code: boolean arrays with --disable-return-size-array + wrote the array length through an uninitialized pointer + (undefined behavior); use a local variable like the other types * Add a test suite (tests/) run by CI; no JDK needed * New example bug_nonstatic_exception demonstrating the exception fix on non-static methods diff --git a/datatypes/booleanDataGiws.py b/datatypes/booleanDataGiws.py index 0f2e986..ae3c97f 100644 --- a/datatypes/booleanDataGiws.py +++ b/datatypes/booleanDataGiws.py @@ -107,12 +107,17 @@ def specificPostProcessing(self, detachThread): if self.isArray(): str = JNIFrameWork().getExceptionCheckProfile(detachThread) strCommon = "" + strDeclaration = "" if configGiws().getDisableReturnSize() is True: - strCommon += "int *lenRow;" + strCommon += "int lenRow;" + else: + # The size of the array is returned as output argument of the + # function + strDeclaration = "*" strCommon += """ - *lenRow = curEnv->GetArrayLength(res); + %s lenRow = curEnv->GetArrayLength(res); jboolean isCopy = JNI_FALSE; - """ + """ % (strDeclaration) if self.getDimensionArray() == 1: return ( @@ -122,30 +127,31 @@ def specificPostProcessing(self, detachThread): /* faster than getXXXArrayElements */ jboolean *resultsArray = static_cast(curEnv->GetPrimitiveArrayCritical(res, &isCopy)); - bool * myArray= new bool[*lenRow]; + bool * myArray= new bool[%s lenRow]; - for (jsize i = 0; i < *lenRow; i++){ + for (jsize i = 0; i < %s lenRow; i++){ myArray[i]=(resultsArray[i] == JNI_TRUE); } curEnv->ReleasePrimitiveArrayCritical(res, resultsArray, JNI_ABORT); curEnv->DeleteLocalRef(res); """ + % (strDeclaration, strDeclaration) ) else: if configGiws().getDisableReturnSize() is True: - str += "int *lenCol;" + str += "int lenCol;" return ( str + strCommon + """ - bool ** myArray = new bool*[*lenRow]; - for(int i=0; i<*lenRow; i++) { + bool ** myArray = new bool*[%s lenRow]; + for(int i=0; i<%s lenRow; i++) { jbooleanArray oneDim = (jbooleanArray)curEnv->GetObjectArrayElement(res, i); - *lenCol=curEnv->GetArrayLength(oneDim); + %s lenCol=curEnv->GetArrayLength(oneDim); bool *resultsArray = static_cast(curEnv->GetPrimitiveArrayCritical(oneDim, &isCopy)); - myArray[i] = new bool[*lenCol]; - for(int j=0; j<*lenCol; j++) { + myArray[i] = new bool[%s lenCol]; + for(int j=0; j<%s lenCol; j++) { myArray[i][j]=(resultsArray[j] == JNI_TRUE); } curEnv->ReleasePrimitiveArrayCritical(res, resultsArray, JNI_ABORT); @@ -153,6 +159,13 @@ def specificPostProcessing(self, detachThread): curEnv->DeleteLocalRef(res); """ + % ( + strDeclaration, + strDeclaration, + strDeclaration, + strDeclaration, + strDeclaration, + ) ) else: From 04221847b7bc32273e7975813a673095bb6d64e7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 7 Aug 2026 10:44:59 +0200 Subject: [PATCH 3/6] Generated classes: make the destructor virtual The generated classes have a virtual method (getCurrentEnv), so deleting an instance was flagged by -Wdelete-non-virtual-dtor and deleting a derived wrapper through a base pointer was undefined behavior. --- CHANGELOG | 3 +++ classRepresentation/objectGiws.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b3d6a60..075c5b0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,9 @@ giws (3.1.0) * Generated code: boolean arrays with --disable-return-size-array wrote the array length through an uninitialized pointer (undefined behavior); use a local variable like the other types + * Generated classes: make the destructor virtual (the class has + virtual methods; deleting through it was flagged by + -Wdelete-non-virtual-dtor) * Add a test suite (tests/) run by CI; no JDK needed * New example bug_nonstatic_exception demonstrating the exception fix on non-static methods diff --git a/classRepresentation/objectGiws.py b/classRepresentation/objectGiws.py index 31bac8d..94742fe 100644 --- a/classRepresentation/objectGiws.py +++ b/classRepresentation/objectGiws.py @@ -428,7 +428,7 @@ def generateCXXHeader(self, packageName): %s // Destructor - ~%s(); + virtual ~%s(); // Generic method // Synchronization methods From c187fc7a9662022e145b35a8e081755b307e9835 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 7 Aug 2026 10:45:08 +0200 Subject: [PATCH 4/6] examples: fix the -Wall warnings in the hand-written sources Unused variables (example3, bug_disable_return, bug_no_param_int_array, inherit/showcase), variable-length arrays in example2 (a Clang extension in C++) and catching the polymorphic JniException by value in example3. Everything now builds with -Wall -Werror with both gcc and clang. --- CHANGELOG | 2 ++ examples/bug_disable_return/main.cpp | 3 ++- examples/bug_no_param_int_array/main.cpp | 3 ++- examples/example2/main.cpp | 4 ++-- examples/example3/main.cpp | 13 ++++++------- examples/inherit/showcase.cpp | 1 + 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 075c5b0..47272df 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,8 @@ giws (3.1.0) * Generated classes: make the destructor virtual (the class has virtual methods; deleting through it was flagged by -Wdelete-non-virtual-dtor) + * Examples: fix the -Wall warnings in the hand-written sources + (unused variables, VLA, catching a polymorphic type by value) * Add a test suite (tests/) run by CI; no JDK needed * New example bug_nonstatic_exception demonstrating the exception fix on non-static methods diff --git a/examples/bug_disable_return/main.cpp b/examples/bug_disable_return/main.cpp index 9d97a92..a1fc784 100644 --- a/examples/bug_disable_return/main.cpp +++ b/examples/bug_disable_return/main.cpp @@ -2,7 +2,7 @@ #include "Plop.hxx" #include /* -Copyright or © or Copr. INRIA/Scilab - Sylvestre LEDRU +Copyright or � or Copr. INRIA/Scilab - Sylvestre LEDRU # Sylvestre LEDRU - @@ -64,5 +64,6 @@ int main(){ JavaVM* jvm = create_vm(); Plop *plop = new Plop(jvm); cout << "Does nothing. It is just to test the bug." << endl; + delete plop; return 0; } diff --git a/examples/bug_no_param_int_array/main.cpp b/examples/bug_no_param_int_array/main.cpp index a87eecb..c5608b7 100644 --- a/examples/bug_no_param_int_array/main.cpp +++ b/examples/bug_no_param_int_array/main.cpp @@ -2,7 +2,7 @@ #include "Bar.hxx" #include /* -Copyright or © or Copr. INRIA/Scilab - Sylvestre LEDRU +Copyright or � or Copr. INRIA/Scilab - Sylvestre LEDRU # Sylvestre LEDRU - @@ -64,5 +64,6 @@ int main(){ JavaVM* jvm = create_vm(); Bar *plop = new Bar(jvm); cout << "Does nothing" << endl; + delete plop; return 0; } diff --git a/examples/example2/main.cpp b/examples/example2/main.cpp index a1aa529..e5f0b40 100644 --- a/examples/example2/main.cpp +++ b/examples/example2/main.cpp @@ -1,5 +1,5 @@ /* -Copyright or © or Copr. INRIA/Scilab - Sylvestre LEDRU +Copyright or � or Copr. INRIA/Scilab - Sylvestre LEDRU # Sylvestre LEDRU - @@ -61,7 +61,7 @@ using namespace example2; using namespace std; int main(){ - int sizeArray=3; + const int sizeArray=3; JavaVM* jvm = create_vm(); MyObjectWithArray *plop = new MyObjectWithArray(jvm); int myArrayOfLong[sizeArray]; diff --git a/examples/example3/main.cpp b/examples/example3/main.cpp index bb789db..379dfec 100644 --- a/examples/example3/main.cpp +++ b/examples/example3/main.cpp @@ -1,5 +1,5 @@ /* -Copyright or © or Copr. INRIA/Scilab - Sylvestre LEDRU +Copyright or � or Copr. INRIA/Scilab - Sylvestre LEDRU # Sylvestre LEDRU - @@ -63,7 +63,6 @@ using namespace example3; using namespace std; int main(){ - int sizeArray=3; JavaVM* jvm = create_vm(); MyObjectWhichReturnsExceptions *plop = new MyObjectWhichReturnsExceptions(jvm); @@ -74,9 +73,9 @@ int main(){ cout << "Exception caught:" << endl; try { - int myIntsWithExceptionCatched = plop->getIntFromArrayOfSizeThree(22); + plop->getIntFromArrayOfSizeThree(22); - }catch(GiwsException::JniException e) { + }catch(const GiwsException::JniException & e) { cout << "getJavaDescription: " << e.getJavaDescription() << endl; cout << "getJavaStackTrace: " << e.getJavaStackTrace() << endl; cout << "getJavaExceptionName: " << e.getJavaExceptionName() << endl; @@ -86,8 +85,8 @@ int main(){ cout << "Exception will be trigger. This will display the message of the exception" << endl; try { - int value = plop->thisMethodWillFailWithMessage(); - }catch(GiwsException::JniException e) { + plop->thisMethodWillFailWithMessage(); + }catch(const GiwsException::JniException & e) { cout << "getJavaDescription: " << e.getJavaDescription() << endl; cout << "getJavaStackTrace: " << e.getJavaStackTrace() << endl; cout << "getJavaExceptionName: " << e.getJavaExceptionName() << endl; @@ -96,7 +95,7 @@ int main(){ cout << "========================" << endl; cout << "Exception not caught:" << endl; - int myIntsWithException = plop->getIntFromArrayOfSizeThree(223); + plop->getIntFromArrayOfSizeThree(223); cout << "Value from the Java with good pos : " << myInts < Date: Fri, 7 Aug 2026 10:45:25 +0200 Subject: [PATCH 5/6] CI: check that the generated code and the examples build with -Wall -Werror After 'make examples' has generated the wrappers, compile every example .cpp (generated and hand-written) with g++ -Wall -Werror -fsyntax-only. --- .github/workflows/ci.yml | 9 +++++++++ CHANGELOG | 2 ++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f7a3de..5064167 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,15 @@ jobs: run: | make examples + - name: Check that the generated code and the examples build with -Wall -Werror + run: | + for f in examples/*/*.cpp; do + echo "Checking $f" + g++ -Wall -Werror -fsyntax-only \ + -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \ + -I"$(dirname "$f")" "$f" + done + package: runs-on: ubuntu-latest strategy: diff --git a/CHANGELOG b/CHANGELOG index 47272df..7278293 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,6 +25,8 @@ giws (3.1.0) -Wdelete-non-virtual-dtor) * Examples: fix the -Wall warnings in the hand-written sources (unused variables, VLA, catching a polymorphic type by value) + * CI: check that the generated code and the examples build with + -Wall -Werror * Add a test suite (tests/) run by CI; no JDK needed * New example bug_nonstatic_exception demonstrating the exception fix on non-static methods From 3b864a1b4cbb2b40a0373a8d041e1cc3d8e240de Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 7 Aug 2026 10:45:36 +0200 Subject: [PATCH 6/6] Release 4.0.0 The 3.1.0 release was never published (the local tag will be replaced): with the generated-code behavior changes accumulated since 3.0.1 (virtual destructor, exception checks on non-static methods, ABI-visible fixes), this is a major version. --- CHANGELOG | 4 ++-- configGiws.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7278293..d137a71 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -giws (3.1.0) +giws (4.0.0) * Generated code: the exception check after the Java call was only emitted for static methods. Non-static methods now detect a pending @@ -33,7 +33,7 @@ giws (3.1.0) * Modernize the Python code (f-strings, type hints, with-statement) * Add a pre-commit configuration running ruff - -- Sylvestre Ledru Thu, 06 Aug 2026 22:23:44 +0200 + -- Sylvestre Ledru Fri, 07 Aug 2026 10:43:07 +0200 giws (2.0.3) diff --git a/configGiws.py b/configGiws.py index 531e455..7a8a9cd 100644 --- a/configGiws.py +++ b/configGiws.py @@ -36,7 +36,7 @@ """Configuration of the Env""" -__version__ = "3.1.0" +__version__ = "4.0.0" class configGiws: