From 27088ac224e150f4cf86c2daaec1c75b7c74b377 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?=
Date: Sun, 15 Apr 2018 18:08:35 +0200
Subject: [PATCH 01/11] COM Call through the
`com.sun.jna.platform.win32.COM.util.Factory` loose stack trace information
The execution of business method invoked via
com.sun.jna.platform.win32.COM.util.Factory is dispatched into a
different thread, that has COM initialized. If the execution of one
of these methods raises an Exception, the exception is unwrapped as
far as possible. This in turn looses the call information on the call
site.
This is corrected here by concatenating the two stacktraces. While
not strictly correct, it results in stack traces, that give enough
information to trace them back to their origin.
---
.../sun/jna/platform/win32/COM/util/Factory.java | 15 +++++++++++++++
.../win32/COM/util/ProxyObjectFactory_Test.java | 11 ++++++++++-
.../COM/util/ProxyObjectObjectFactory_Test.java | 9 +++++++++
3 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Factory.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Factory.java
index f65da63663..f2c1c9a6d6 100644
--- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Factory.java
+++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Factory.java
@@ -177,10 +177,12 @@ private T runInComThread(Callable callable) {
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
if (cause instanceof RuntimeException) {
+ appendStacktrace(ex, cause);
throw (RuntimeException) cause;
} else if (cause instanceof InvocationTargetException) {
cause = ((InvocationTargetException) cause).getTargetException();
if (cause instanceof RuntimeException) {
+ appendStacktrace(ex, cause);
throw (RuntimeException) cause;
}
}
@@ -188,6 +190,19 @@ private T runInComThread(Callable callable) {
}
}
+ /**
+ * Append the stack trace available via caughtException to the stack trace
+ * of toBeThrown. The combined stack trace is reassigned to toBeThrown
+ */
+ private static void appendStacktrace(Exception caughtException, Throwable toBeThrown) {
+ StackTraceElement[] upperTrace = caughtException.getStackTrace();
+ StackTraceElement[] lowerTrace = toBeThrown.getStackTrace();
+ StackTraceElement[] trace = new StackTraceElement[upperTrace.length + lowerTrace.length];
+ System.arraycopy(upperTrace, 0, trace, lowerTrace.length, upperTrace.length);
+ System.arraycopy(lowerTrace, 0, trace, 0, lowerTrace.length);
+ toBeThrown.setStackTrace(trace);
+ }
+
public ComThread getComThread() {
return comThread;
}
diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectFactory_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectFactory_Test.java
index 4c66f4d5a1..d543bc7f81 100644
--- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectFactory_Test.java
+++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectFactory_Test.java
@@ -154,8 +154,17 @@ public void testFetchNotExistingObject() {
assertNotNull("fetchObject on a non-running Object must raise an exception", exceptionRaised);
assertEquals("Unexpected error code", exceptionRaised.getHresult().intValue(), WinError.MK_E_UNAVAILABLE);
assertTrue("Error code not matched", exceptionRaised.matchesErrorCode(WinError.MK_E_UNAVAILABLE));
+ boolean callingMethodPartOfStackTrace = false;
+ for(StackTraceElement ste: exceptionRaised.getStackTrace()) {
+ if("testFetchNotExistingObject".equals(ste.getMethodName())
+ && getClass().getName().equals(ste.getClassName())) {
+ callingMethodPartOfStackTrace = true;
+ break;
+ }
+ }
+ assertTrue("The calling method must be part of the reported stack trace", callingMethodPartOfStackTrace);
}
-
+
@Test
public void equals() {
MsWordApp comObj1 = this.factory.createObject(MsWordApp.class);
diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java
index 3bb51065ff..f1753a0b55 100644
--- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java
+++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java
@@ -158,6 +158,15 @@ public void testFetchNotExistingObject() {
assertNotNull("fetchObject on a non-running Object must raise an exception", exceptionRaised);
assertEquals("Unexpected error code", exceptionRaised.getHresult().intValue(), WinError.MK_E_UNAVAILABLE);
assertTrue("Error code not matched", exceptionRaised.matchesErrorCode(WinError.MK_E_UNAVAILABLE));
+ boolean callingMethodPartOfStackTrace = false;
+ for(StackTraceElement ste: exceptionRaised.getStackTrace()) {
+ if("testFetchNotExistingObject".equals(ste.getMethodName())
+ && getClass().getName().equals(ste.getClassName())) {
+ callingMethodPartOfStackTrace = true;
+ break;
+ }
+ }
+ assertTrue("The calling method must be part of the reported stack trace", callingMethodPartOfStackTrace);
}
@Test
From 38ba944b5c7d6a7457909bb7bd337a3651c24d88 Mon Sep 17 00:00:00 2001
From: James Howard
Date: Thu, 29 Mar 2018 14:51:38 -0500
Subject: [PATCH 02/11] Add ACL constructor that tolerates files with unknown
ACE types
If an ACL contained supported (ACCESS_ALLOWED_ACE_TYPE, ACCESS_DENIED_ACE_TYPE)
and unsupported types (i.e. ACCESS_ALLOWED_CALLBACK_ACE_TYPE) the constructor
threw an IllegalArgumentException.
The extraction logic for the ACEs was moved to the accessor function and
unsupported ACEs are skipped.
---
.../src/com/sun/jna/platform/win32/WinNT.java | 43 ++++++++++++-------
1 file changed, 27 insertions(+), 16 deletions(-)
diff --git a/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java b/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java
index 884f7a40de..fbfb929240 100644
--- a/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java
+++ b/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java
@@ -23,6 +23,7 @@
*/
package com.sun.jna.platform.win32;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -2553,8 +2554,6 @@ public static class ACL extends Structure {
public short AceCount;
public short Sbz2;
- private ACCESS_ACEStructure[] ACEs;
-
public ACL() {
super();
}
@@ -2567,30 +2566,42 @@ public ACL(int size) {
public ACL(Pointer pointer) {
super(pointer);
read();
- ACEs = new ACCESS_ACEStructure[AceCount];
+ }
+
+ public ACCESS_ACEStructure[] getACEStructures() {
+ return getACEStructures(false);
+ }
+
+ public ACCESS_ACEStructure[] getACEStructures(boolean tolerateUnknownAceTypes) {
+ final List ACEs = new ArrayList(AceCount);
+ final Pointer pointer = this.getPointer();
int offset = size();
for (int i = 0; i < AceCount; i++) {
- Pointer share = pointer.share(offset);
- // ACE_HEADER.AceType
- final byte aceType = share.getByte(0);
- ACCESS_ACEStructure ace;
+ final Pointer share = pointer.share(offset);
+ final byte aceType = share.getByte(0); // ACE_HEADER.AceType
+ final short aceSize;
switch (aceType) {
case ACCESS_ALLOWED_ACE_TYPE:
- ace = new ACCESS_ALLOWED_ACE(share);
+ final ACCESS_ALLOWED_ACE allowedAce = new ACCESS_ALLOWED_ACE(share);
+ aceSize = allowedAce.AceSize;
+ ACEs.add(allowedAce);
break;
case ACCESS_DENIED_ACE_TYPE:
- ace = new ACCESS_DENIED_ACE(share);
+ final ACCESS_DENIED_ACE deniedAce = new ACCESS_DENIED_ACE(share);
+ aceSize = deniedAce.AceSize;
+ ACEs.add(deniedAce);
break;
default:
- throw new IllegalArgumentException("Unknown ACE type " + aceType);
+ if (! tolerateUnknownAceTypes) {
+ throw new IllegalStateException("Unknown ACE type " + aceType);
+ }
+ final ACE_HEADER aceHeader = new ACE_HEADER(share);
+ aceSize = aceHeader.AceSize;
+ break;
}
- ACEs[i] = ace;
- offset += ace.AceSize;
+ offset += aceSize;
}
- }
-
- public ACCESS_ACEStructure[] getACEStructures() {
- return ACEs;
+ return ACEs.toArray(new ACCESS_ACEStructure[ACEs.size()]);
}
@Override
From ab72bdb44141dd2fe88286d750aec337844483d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?=
Date: Sun, 8 Apr 2018 21:03:47 +0200
Subject: [PATCH 03/11] Unify ACEStructure and ACE_HEADER and return
ACE_HEADER[] from ACL
Instead of throwing an Exception or skipping ACEs, alle ACEs in an ACL
are returned. If the ACE is an unsupported type, the base structure
of all ACEs (ACE_HEADER) is returned.
Users of the ACEs contained in the ACL need to check the type and
downcast appropriately.
---
CHANGES.md | 9 ++
.../sun/jna/platform/win32/Advapi32Util.java | 49 +++++++----
.../src/com/sun/jna/platform/win32/WinNT.java | 84 ++++++++-----------
3 files changed, 74 insertions(+), 68 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 8aac775410..0d71935e78 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -15,6 +15,7 @@ Features
* [#797](https://github.com/java-native-access/jna/issues/797): Binding `Advapi32#EnumDependendServices`, `Advapi32#EnumServicesStatusEx` and `Advapi32#QueryServiceStatus`. `W32Service#stopService` was modified to be more resilent when stopping service - [@matthiasblaesing](https://github.com/matthiasblaesing).
* Bind `com.sun.jna.platform.win32.Kernel32.ExpandEnvironmentStrings` and add helper method for it as `Kernel32Util#expandEnvironmentStrings` - [@matthiasblaesing](https://github.com/matthiasblaesing).
* [#935](https://github.com/java-native-access/jna/pull/935): Add RegConnectRegistry to Advapi32 mappings. - [@cxcorp](https://github.com/cxcorp).
+* [#947](https://github.com/java-native-access/jna/pull/947): Allow retrieval of `ACEs` from `com.sun.jna.platform.win32.WinNT.ACL` even if the contained `ACE` is not currently supported - [@jrobhoward](https://github.com/jrobhoward).
Bug Fixes
---------
@@ -84,6 +85,14 @@ Breaking Changes
was changed from `PointerByReference` to `STRRET` and matching this,
the first parameter of `com.sun.jna.platform.win32.Shlwapi.StrRetToStr` was
changed identically.
+* `ACE_HEADER` replaces `ACEStructure` as the base class for `ACEs`.
+ `com.sun.jna.platform.win32.WinNT.ACL` was modified to support ACLS, that contain
+ `ACEs` other than `ACCESS_ALLOWED_ACE_TYPE` and `ACCESS_DENIED_ACE_TYPE` by
+ widening the return type of `getACEStructures` to `ACE_HEADER[]` and renaming
+ the method to `getACEs`. In
+ consequence `com.sun.jna.platform.win32.Advapi32Util#getFileSecurity` was
+ changed similarly. The SID accessors `getSidString` and `getSID` were moved
+ from `ACEStructure` to `ACCESS_ACEStructure`.
Release 4.5.0
=============
diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java
index afa5985954..2c4eff7632 100755
--- a/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java
+++ b/contrib/platform/src/com/sun/jna/platform/win32/Advapi32Util.java
@@ -72,6 +72,7 @@
import com.sun.jna.platform.win32.WinDef.ULONGByReference;
import com.sun.jna.platform.win32.WinNT.ACCESS_ACEStructure;
import com.sun.jna.platform.win32.WinNT.ACCESS_ALLOWED_ACE;
+import com.sun.jna.platform.win32.WinNT.ACE_HEADER;
import com.sun.jna.platform.win32.WinNT.ACL;
import com.sun.jna.platform.win32.WinNT.EVENTLOGRECORD;
import com.sun.jna.platform.win32.WinNT.GENERIC_MAPPING;
@@ -91,6 +92,7 @@
import com.sun.jna.ptr.LongByReference;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.win32.W32APITypeMapper;
+import java.util.List;
/**
* Advapi32 utility API.
@@ -2200,7 +2202,12 @@ public void remove() {
}
}
- public static ACCESS_ACEStructure[] getFileSecurity(String fileName,
+ /**
+ * @param fileName path to the file
+ * @param compact if true compatible ACEs are merged if possible
+ * @return
+ */
+ public static ACE_HEADER[] getFileSecurity(String fileName,
boolean compact) {
int infoType = WinNT.DACL_SECURITY_INFORMATION;
int nLength = 1024;
@@ -2231,29 +2238,35 @@ public static ACCESS_ACEStructure[] getFileSecurity(String fileName,
SECURITY_DESCRIPTOR_RELATIVE sdr = new WinNT.SECURITY_DESCRIPTOR_RELATIVE(
memory);
- memory.clear();
ACL dacl = sdr.getDiscretionaryACL();
- ACCESS_ACEStructure[] aceStructures = dacl.getACEStructures();
+ ACE_HEADER[] aceStructures = dacl.getACEs();
if (compact) {
+ List result = new ArrayList();
Map aceMap = new HashMap();
- for (ACCESS_ACEStructure aceStructure : aceStructures) {
- boolean inherted = ((aceStructure.AceFlags & WinNT.VALID_INHERIT_FLAGS) != 0);
- String key = aceStructure.getSidString() + "/" + inherted + "/"
- + aceStructure.getClass().getName();
- ACCESS_ACEStructure aceStructure2 = aceMap.get(key);
- if (aceStructure2 != null) {
- int accessMask = aceStructure2.Mask;
- accessMask = accessMask | aceStructure.Mask;
- aceStructure2.Mask = accessMask;
- } else {
- aceMap.put(key, aceStructure);
- }
+ for (ACE_HEADER aceStructure : aceStructures) {
+ if(aceStructure instanceof ACCESS_ACEStructure) {
+ ACCESS_ACEStructure accessACEStructure = (ACCESS_ACEStructure) aceStructure;
+ boolean inherted = ((aceStructure.AceFlags & WinNT.VALID_INHERIT_FLAGS) != 0);
+ String key = accessACEStructure.getSidString() + "/" + inherted + "/"
+ + aceStructure.getClass().getName();
+ ACCESS_ACEStructure aceStructure2 = aceMap.get(key);
+ if (aceStructure2 != null) {
+ int accessMask = aceStructure2.Mask;
+ accessMask = accessMask | accessACEStructure.Mask;
+ aceStructure2.Mask = accessMask;
+ } else {
+ aceMap.put(key, accessACEStructure);
+ result.add(aceStructure2);
+ }
+ } else {
+ result.add(aceStructure);
+ }
}
- return aceMap.values().toArray(
- new ACCESS_ACEStructure[aceMap.size()]);
+ return result.toArray(new ACE_HEADER[result.size()]);
}
- return aceStructures;
+
+ return aceStructures;
}
public static enum AccessCheckPermission {
diff --git a/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java b/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java
index fbfb929240..658240e6a9 100644
--- a/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java
+++ b/contrib/platform/src/com/sun/jna/platform/win32/WinNT.java
@@ -2568,40 +2568,36 @@ public ACL(Pointer pointer) {
read();
}
- public ACCESS_ACEStructure[] getACEStructures() {
- return getACEStructures(false);
- }
-
- public ACCESS_ACEStructure[] getACEStructures(boolean tolerateUnknownAceTypes) {
- final List ACEs = new ArrayList(AceCount);
+ /**
+ * Extract the contained ACEs from the ACL.
+ *
+ * ACE types as decoded to their native JNA counterparts. ACE types,
+ * that are currently unsupported by JNA are returned as
+ * {@link WinNT.ACE_HEADER} objects.
+ *
+ * @return array holding the contained ACEs
+ */
+ public ACE_HEADER[] getACEs() {
+ ACE_HEADER[] ACEs = new ACE_HEADER[AceCount];
final Pointer pointer = this.getPointer();
int offset = size();
for (int i = 0; i < AceCount; i++) {
final Pointer share = pointer.share(offset);
final byte aceType = share.getByte(0); // ACE_HEADER.AceType
- final short aceSize;
switch (aceType) {
case ACCESS_ALLOWED_ACE_TYPE:
- final ACCESS_ALLOWED_ACE allowedAce = new ACCESS_ALLOWED_ACE(share);
- aceSize = allowedAce.AceSize;
- ACEs.add(allowedAce);
+ ACEs[i] = new ACCESS_ALLOWED_ACE(share);
break;
case ACCESS_DENIED_ACE_TYPE:
- final ACCESS_DENIED_ACE deniedAce = new ACCESS_DENIED_ACE(share);
- aceSize = deniedAce.AceSize;
- ACEs.add(deniedAce);
+ ACEs[i] = new ACCESS_DENIED_ACE(share);
break;
default:
- if (! tolerateUnknownAceTypes) {
- throw new IllegalStateException("Unknown ACE type " + aceType);
- }
- final ACE_HEADER aceHeader = new ACE_HEADER(share);
- aceSize = aceHeader.AceSize;
+ ACEs[i] = new ACE_HEADER(share);
break;
}
- offset += aceSize;
+ offset += ACEs[i].AceSize;
}
- return ACEs.toArray(new ACCESS_ACEStructure[ACEs.size()]);
+ return ACEs;
}
@Override
@@ -2712,63 +2708,41 @@ protected List getFieldOrder() {
}
}
- public static abstract class ACEStructure extends Structure {
+ public static class ACE_HEADER extends Structure {
public static final List FIELDS = createFieldsOrder("AceType", "AceFlags", "AceSize");
public byte AceType;
public byte AceFlags;
public short AceSize;
- PSID psid;
-
- public ACEStructure() {
+ public ACE_HEADER() {
super();
}
- public ACEStructure(Pointer p) {
+ public ACE_HEADER(Pointer p) {
super(p);
+ read();
}
- public ACEStructure(byte AceType, byte AceFlags, short AceSize, PSID psid) {
+ public ACE_HEADER(byte AceType, byte AceFlags, short AceSize) {
super();
this.AceType = AceType;
this.AceFlags = AceFlags;
this.AceSize = AceSize;
- this.psid = psid;
write();
}
- public String getSidString() {
- return Advapi32Util.convertSidToStringSid(psid);
- }
-
- public PSID getSID() {
- return psid;
- }
-
@Override
protected List getFieldOrder() {
return FIELDS;
}
}
-
- /* ACE header */
- public static class ACE_HEADER extends ACEStructure {
- public ACE_HEADER() {
- super();
- }
-
- public ACE_HEADER(Pointer p) {
- super(p);
- read();
- }
- }
-
+
/**
* ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACE have the same structure layout
*/
- public static abstract class ACCESS_ACEStructure extends ACEStructure {
- public static final List FIELDS = createFieldsOrder(ACEStructure.FIELDS, "Mask", "SidStart");
+ public static abstract class ACCESS_ACEStructure extends ACE_HEADER {
+ public static final List FIELDS = createFieldsOrder(ACE_HEADER.FIELDS, "Mask", "SidStart");
public int Mask;
/**
@@ -2777,6 +2751,8 @@ public static abstract class ACCESS_ACEStructure extends ACEStructure {
*/
public byte[] SidStart = new byte[4];
+ PSID psid;
+
public ACCESS_ACEStructure() {
super();
}
@@ -2799,6 +2775,14 @@ public ACCESS_ACEStructure(Pointer p) {
read();
}
+ public String getSidString() {
+ return Advapi32Util.convertSidToStringSid(psid);
+ }
+
+ public PSID getSID() {
+ return psid;
+ }
+
/**
* Write override due to psid not being a managed field
*/
From 4bfcb1183f93f3fdca2f405e1a59096430a31a5f Mon Sep 17 00:00:00 2001
From: Andreas Loth
Date: Mon, 16 Apr 2018 21:48:19 +0200
Subject: [PATCH 04/11] Add string <--> byte[] conversion API using Charset
---
src/com/sun/jna/Native.java | 107 ++++++++++++++++++++++---------
test/com/sun/jna/NativeTest.java | 21 ++++++
2 files changed, 99 insertions(+), 29 deletions(-)
diff --git a/src/com/sun/jna/Native.java b/src/com/sun/jna/Native.java
index bc4113a06c..0068ffa022 100644
--- a/src/com/sun/jna/Native.java
+++ b/src/com/sun/jna/Native.java
@@ -48,6 +48,8 @@
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
@@ -107,7 +109,8 @@
*/
public final class Native implements Version {
- public static final String DEFAULT_ENCODING = Charset.defaultCharset().name();
+ public static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
+ public static final String DEFAULT_ENCODING = Native.DEFAULT_CHARSET.name();
public static boolean DEBUG_LOAD = Boolean.getBoolean("jna.debug_load");
public static boolean DEBUG_JNA_LOAD = Boolean.getBoolean("jna.debug_load.jna");
@@ -346,6 +349,35 @@ public static Pointer getDirectBufferPointer(Buffer b) {
private static native long _getDirectBufferPointer(Buffer b);
+ /**
+ * Gets the charset belonging to the given {@code encoding}.
+ * @param encoding The encoding - if {@code null} then the default platform
+ * encoding is used.
+ * @return The charset belonging to the given {@code encoding} or the platform default.
+ * Never {@code null}.
+ */
+ private static Charset getCharset(String encoding) {
+ Charset charset = null;
+ if (encoding != null) {
+ try {
+ charset = Charset.forName(encoding);
+ }
+ catch(IllegalCharsetNameException e) {
+ System.err.println("JNA Warning: Encoding '"
+ + encoding + "' is unsupported (" + e.getMessage() + ")");
+ }
+ catch(UnsupportedCharsetException e) {
+ System.err.println("JNA Warning: Encoding '"
+ + encoding + "' is unsupported (" + e.getMessage() + ")");
+ }
+ }
+ if (charset == null) {
+ System.err.println("JNA Warning: Using fallback encoding " + System.getProperty("file.encoding"));
+ charset = Native.DEFAULT_CHARSET;
+ }
+ return charset;
+ }
+
/**
* Obtain a Java String from the given native byte array. If there is
* no NUL terminator, the String will comprise the entire array. The
@@ -367,11 +399,27 @@ public static String toString(byte[] buf) {
* holds a {@code char} array. This means only single-byte encodings are
* supported.
*
- * @param buf The buffer containing the encoded bytes
+ * @param buf The buffer containing the encoded bytes. Must not be {@code null}.
* @param encoding The encoding name - if {@code null} then the platform
* default encoding will be used
*/
public static String toString(byte[] buf, String encoding) {
+ return Native.toString(buf, Native.getCharset(encoding));
+ }
+
+ /**
+ * Obtain a Java String from the given native byte array, using the given
+ * encoding. If there is no NUL terminator, the String will comprise the
+ * entire array.
+ *
+ * Usage note: This function assumes, that {@code buf}
+ * holds a {@code char} array. This means only single-byte encodings are
+ * supported.
+ *
+ * @param buf The buffer containing the encoded bytes. Must not be {@code null}.
+ * @param charset The charset to decode {@code buf}. Must not be {@code null}.
+ */
+ public static String toString(byte[] buf, Charset charset) {
int len = buf.length;
// find out the effective length
for (int index = 0; index < len; index++) {
@@ -385,18 +433,7 @@ public static String toString(byte[] buf, String encoding) {
return "";
}
- if (encoding != null) {
- try {
- return new String(buf, 0, len, encoding);
- }
- catch(UnsupportedEncodingException e) {
- System.err.println("JNA Warning: Encoding '"
- + encoding + "' is unsupported");
- }
- }
-
- System.err.println("JNA Warning: Decoding with fallback " + System.getProperty("file.encoding"));
- return new String(buf, 0, len);
+ return new String(buf, 0, len, charset);
}
/**
@@ -744,7 +781,7 @@ static byte[] getBytes(String s) {
}
/**
- * @param s The string
+ * @param s The string. Must not be {@code null}.
* @param encoding The encoding - if {@code null} then the default platform
* encoding is used
* @return A byte array corresponding to the given String, using the given
@@ -752,18 +789,17 @@ static byte[] getBytes(String s) {
* encoding.
*/
static byte[] getBytes(String s, String encoding) {
- if (encoding != null) {
- try {
- return s.getBytes(encoding);
- }
- catch(UnsupportedEncodingException e) {
- System.err.println("JNA Warning: Encoding '"
- + encoding + "' is unsupported");
- }
- }
- System.err.println("JNA Warning: Encoding with fallback "
- + System.getProperty("file.encoding"));
- return s.getBytes();
+ return Native.getBytes(s, Native.getCharset(encoding));
+ }
+
+ /**
+ * @param s The string. Must not be {@code null}.
+ * @param charset The charset used to encode {@code s}. Must not be {@code null}.
+ * @return A byte array corresponding to the given String, using the given
+ * charset.
+ */
+ static byte[] getBytes(String s, Charset charset) {
+ return s.getBytes(charset);
}
/**
@@ -777,13 +813,26 @@ public static byte[] toByteArray(String s) {
}
/**
- * @param s The string
+ * @param s The string. Must not be {@code null}.
+ * @param encoding The encoding - if {@code null} then the default platform
+ * encoding is used
* @return A NUL-terminated byte buffer equivalent to the given String,
* using the given encoding.
* @see #getBytes(String, String)
*/
public static byte[] toByteArray(String s, String encoding) {
- byte[] bytes = getBytes(s, encoding);
+ return Native.toByteArray(s, Native.getCharset(encoding));
+ }
+
+ /**
+ * @param s The string. Must not be {@code null}.
+ * @param charset The charset used to encode {@code s}. Must not be {@code null}.
+ * @return A NUL-terminated byte buffer equivalent to the given String,
+ * using the given charset.
+ * @see #getBytes(String, String)
+ */
+ public static byte[] toByteArray(String s, Charset charset) {
+ byte[] bytes = Native.getBytes(s, charset);
byte[] buf = new byte[bytes.length+1];
System.arraycopy(bytes, 0, buf, 0, bytes.length);
return buf;
diff --git a/test/com/sun/jna/NativeTest.java b/test/com/sun/jna/NativeTest.java
index 132e695f06..b5443a4ae5 100644
--- a/test/com/sun/jna/NativeTest.java
+++ b/test/com/sun/jna/NativeTest.java
@@ -391,6 +391,15 @@ public void testToByteArrayWithEncoding() throws Exception {
assertEquals("Wrong byte array contents", VALUE, new String(buf, 0, buf.length-1, ENCODING));
}
+ public void testToByteArrayWithCharset() throws Exception {
+ final Charset CHARSET = Charset.forName("UTF-8");
+ final String VALUE = getName() + UNICODE;
+ byte[] buf = Native.toByteArray(VALUE, CHARSET);
+ assertEquals("Wrong byte array length", VALUE.getBytes(CHARSET).length+1, buf.length);
+ assertEquals("Missing NUL terminator", (byte)0, buf[buf.length-1]);
+ assertEquals("Wrong byte array contents", VALUE, new String(buf, 0, buf.length-1, CHARSET));
+ }
+
public void testToCharArray() {
final String VALUE = getName() + UNICODE;
char[] buf = Native.toCharArray(VALUE);
@@ -443,6 +452,12 @@ public void testStringConversionWithEncoding() throws Exception {
assertEquals("Encoded C string improperly converted", getName() + UNICODE, Native.toString(buf, "utf8"));
}
+ public void testStringConversionWithCharset() throws Exception {
+ final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
+ byte[] buf = (getName() + UNICODE + NUL).getBytes(CHARSET_UTF8);
+ assertEquals("Encoded C string improperly converted", getName() + UNICODE, Native.toString(buf, CHARSET_UTF8));
+ }
+
public void testWideStringConversion() {
char[] buf = (getName() + NUL).toCharArray();
assertEquals("Wide C string improperly converted", getName(), Native.toString(buf));
@@ -453,6 +468,12 @@ public void testGetBytes() throws Exception {
assertEquals("Incorrect native bytes from Java String", getName() + UNICODE, new String(buf, "utf8"));
}
+ public void testGetBytesWithCharset() throws Exception {
+ final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
+ byte[] buf = Native.getBytes(getName() + UNICODE, CHARSET_UTF8);
+ assertEquals("Incorrect native bytes from Java String", getName() + UNICODE, new String(buf, CHARSET_UTF8));
+ }
+
public void testGetBytesBadEncoding() throws Exception {
byte[] buf = Native.getBytes(getName(), "unsupported");
assertEquals("Incorrect fallback bytes with bad encoding",
From 11bc1d4c681c86a3935a529b7b94d5ff0a525791 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?=
Date: Sat, 24 Mar 2018 17:21:15 +0100
Subject: [PATCH 05/11] Support building on JDK 10 (minimum build JDK is now 8,
run time java version is 6)
- Support for JDK 10 requires removal of usage of javah, which was
replaced by the javac of JDK8, which can generate headers while
processing the java classes (bumping the minimum build JDK to 8)
- A new build target "native-build-package" was introduced, to
create a zip file with the files necessary to build the native
libraries, only requireing the JDK present on the build system,
but can be an older JDK.
Properties to select the build.os.name, build.os.arch and build.os.endianess
were added to allow cross-system generation of the build package
- the native Makefile is not modified by the build.xml anymore, as the
two parameters JNA_JNI_VERSION and CHECKSUM are already passed in via
command line parameters
- Support for "-d32" and "-d64" datamodel selectors was removed
as it was only needed for solaris and JDK8 (minimum build JDK)
on solaris only supports 64 bit
Closes: #902
Closes: #943
---
CHANGES.md | 12 ++
build.xml | 211 ++++++++++++++++++++--------------
native/Makefile | 6 +-
src/com/sun/jna/Function.java | 6 +
4 files changed, 145 insertions(+), 90 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 0d71935e78..bafdaf5a5a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -30,6 +30,18 @@ Bug Fixes
* [#894](https://github.com/java-native-access/jna/issues/894): NullPointerException can be caused by calling `com.sun.jna.platform.win32.COM.util.ProxyObject#dispose` multiple times - [@matthiasblaesing](https://github.com/matthiasblaesing).
* [#925](https://github.com/java-native-access/jna/issues/925): Optimize `Structure#validate` and prevent `ArrayIndexOutOfBoundsException` in `SAFEARRAY#read` for zero dimensions - [@matthiasblaesing](https://github.com/matthiasblaesing).
* [#340](https://github.com/java-native-access/jna/issues/340): Guard registry handling against out-of-bounds reads by ensuring all read strings are NULL terminated - [@matthiasblaesing](https://github.com/matthiasblaesing).
+* [#902](https://github.com/java-native-access/jna/issues/902): Allow building JNA on JDK 10. `javah`
+ was removed from the JDK and `javac` is now used to create the necessary headers.
+ JNA now has JDK 8 as the minimum build version, at runtime Java 6 is the minimum version.
+ Native code for platforms with a JDK version lower than 8 can still be build by (demonstrated for Solaris x86):
+
+ - Run `ant -Dbuild.os.name=SunOS -Dbuild.os.arch=x86 native-build-package`
+ - Transfer the `build/build-package-sunos-x86-5.2.1.zip` file to the target system. The file holds the native sources, the necessary headers and and a shell script for the build.
+ - Expand the zip on the target system.
+ - Setup `JAVA_HOME` to point to the JDK sources.
+ - Change into the expanded directory and run `bash build.sh`.
+ - The resulting `sunos-x86.jar` is copied back to the original build system to `lib/native/sunos-x86.jar`
+
- [@matthiasblaesing](https://github.com/matthiasblaesing).
Breaking Changes
----------------
diff --git a/build.xml b/build.xml
index 38ae002965..e70b59fb22 100644
--- a/build.xml
+++ b/build.xml
@@ -156,6 +156,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -185,43 +199,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -239,62 +216,62 @@
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -314,16 +291,19 @@
value="${java.home}/lib/${libarch}/libjsig.so" else="">
-
+
+
-
+
+
+
@@ -336,10 +316,11 @@
java.home=${java.home}
java.library.path=${java.library.path}
os.prefix=${os.prefix}
- os.name=${os.name}
- os.arch=${os.arch} (${sun.cpu.endian})
+ os.name=${build.os.name}
+ os.arch=${build.os.arch} (${build.os.endianess})
build=${build}
build.native=${build.native}
+ build.headers=${build.headers}
build.aar=${build.aar}
@@ -377,7 +358,8 @@
includeantruntime="false"
deprecation="on"
debug="${debug}"
- encoding="UTF-8">
+ encoding="UTF-8"
+ nativeheaderdir="${build.headers}">
@@ -665,11 +647,6 @@ osname=macosx;processor=x86;processor=x86-64;processor=ppc
-
-
-
-
-
@@ -681,12 +658,12 @@ osname=macosx;processor=x86;processor=x86-64;processor=ppc
-
-
+
@@ -703,6 +680,10 @@ osname=macosx;processor=x86;processor=x86-64;processor=ppc
flags="m"
file="${md5.file}"/>
+
+
+
+
@@ -755,7 +736,7 @@ osname=macosx;processor=x86;processor=x86-64;processor=ppc
-
+
@@ -790,16 +771,8 @@ osname=macosx;processor=x86;processor=x86-64;processor=ppc
file="${rsrc}" byline="true"/>
-
+
-
-
-
@@ -840,7 +813,7 @@ osname=macosx;processor=x86;processor=x86-64;processor=ppc
-
+
@@ -899,24 +872,24 @@ osname=macosx;processor=x86;processor=x86-64;processor=ppc
-
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
@@ -928,10 +901,73 @@ osname=macosx;processor=x86;processor=x86-64;processor=ppc
+
+
+ #!/bin/sh
+cwd=$(pwd)
+if [ -z "$JAVA_HOME" ]; then
+ echo "Please make sure JAVA_HOME is set"
+ exit 1
+fi
+cd native
+${make}\
+ JAVA_HOME=$JAVA_HOME\
+ JAVAH=$cwd/headers\
+ DEBUG=${debug.native}\
+ CFLAGS_EXTRA=${cflags_extra.native}\
+ DYNAMIC_LIBFFI=${dynlink.native}\
+ ${make.CC}\
+ ${make.USE_MSVC}\
+ BUILD=../build\
+ ${make.SDKROOT}\
+ ${make.ARCH}\
+ ${make.PATH}\
+ ${make.OS}\
+ ${make.OPTS}\
+ JNA_JNI_VERSION=${jni.version}\
+ CHECKSUM=${jni.md5}
+cd ..
+cd build
+zip ../${os.prefix}.jar libjnidispatch.so jnidispatch.dll
+cd ..
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -1132,7 +1168,6 @@ osname=macosx;processor=x86;processor=x86-64;processor=ppc
-
diff --git a/native/Makefile b/native/Makefile
index 6f9ad25bdc..1bea2dc77b 100644
--- a/native/Makefile
+++ b/native/Makefile
@@ -336,7 +336,9 @@ endif
ifeq ($(CC),gcc)
GCC_MAJOR_VERSION = $(shell gcc -dumpversion | cut -f 1 -d '.')
ifneq ($(GCC_MAJOR_VERSION),4)
- LOC_CC_OPTS=-Wno-unknown-warning-option -Werror -Wno-clobbered -Wno-unused-variable
+ ifneq ($(GCC_MAJOR_VERSION),3)
+ LOC_CC_OPTS=-Wno-unknown-warning-option -Werror -Wno-clobbered -Wno-unused-variable
+ endif
endif
else
LOC_CC_OPTS=-Wno-unknown-warning-option -Werror -Wno-clobbered -Wno-unused-variable
@@ -478,7 +480,7 @@ $(FFI_LIB):
@mkdir -p $(FFI_BUILD)
@if [ ! -f $(FFI_SRC)/configure ]; then \
echo "Generating configure"; \
- (cd $(FFI_SRC); ./autogen.sh); \
+ (cd $(FFI_SRC); /bin/sh autogen.sh); \
fi
@if [ ! -f $(FFI_BUILD)/Makefile ]; then \
echo "Configuring libffi ($(ARCH))"; \
diff --git a/src/com/sun/jna/Function.java b/src/com/sun/jna/Function.java
index 2d31827f02..300a982d00 100644
--- a/src/com/sun/jna/Function.java
+++ b/src/com/sun/jna/Function.java
@@ -60,17 +60,23 @@ public interface PostCallRead {
}
/** Maximum number of arguments supported by a JNA function call. */
+ @java.lang.annotation.Native
public static final int MAX_NARGS = 256;
/** Standard C calling convention. */
+ @java.lang.annotation.Native
public static final int C_CONVENTION = 0;
/** First alternate convention (currently used only for w32 stdcall). */
+ @java.lang.annotation.Native
public static final int ALT_CONVENTION = 0x3F;
+ @java.lang.annotation.Native
private static final int MASK_CC = 0x3F;
/** Whether to throw an exception if last error is non-zero after call. */
+ @java.lang.annotation.Native
public static final int THROW_LAST_ERROR = 0x40;
/** Mask for number of fixed args (1-3) for varargs calls. */
+ @java.lang.annotation.Native
public static final int USE_VARARGS = 0x180;
static final Integer INTEGER_TRUE = Integer.valueOf(-1);
From 83cb5cb8f4446f02ab882655bb7d84e2938cd8df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?=
Date: Thu, 26 Apr 2018 00:37:25 +0200
Subject: [PATCH 06/11] Fix travis build (requires newer ant version)
---
.travis.yml | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 7ea0e88fb7..8a6514f6c9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,12 +3,8 @@ dist: trusty
language: java
-addons:
- apt:
- packages:
- - ant-optional
-
install:
+ - '[ "${TRAVIS_OS_NAME}" = "linux" ] && wget http://apache.mirror.iphh.net/ant/binaries/apache-ant-1.9.11-bin.tar.gz && tar xzf apache-ant-1.9.11-bin.tar.gz && sudo mv apache-ant-1.9.11 /usr/local/apache-ant-1.9.11 && sudo rm -f /usr/local/ant && sudo ln -s /usr/local/apache-ant-1.9.11 /usr/local/ant && sudo ln -s /usr/local/apache-ant-1.9.11/bin/ant /usr/local/bin/ant || true'
- '[ "${TRAVIS_OS_NAME}" = "osx" ] && brew update || true'
- '[ "${TRAVIS_OS_NAME}" = "osx" ] && brew uninstall libtool && brew install libtool || true'
- '[ "${TRAVIS_OS_NAME}" = "osx" ] && brew install ant || true'
From 2325c457bedd69e71ee0c0db78d44b143c2771eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?=
Date: Sat, 24 Mar 2018 17:21:33 +0100
Subject: [PATCH 07/11] Update native binary for SunOS/Solaris
x86/sparc/sparcv9
---
lib/native/sunos-sparc.jar | Bin 508 -> 48846 bytes
lib/native/sunos-sparcv9.jar | Bin 508 -> 50436 bytes
lib/native/sunos-x86.jar | Bin 508 -> 48041 bytes
3 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/lib/native/sunos-sparc.jar b/lib/native/sunos-sparc.jar
index 6716399ab65ff2aa137fbe6021402a820a487975..35fa23c7af2b5b38e8f00327285d103476d3e3d6 100644
GIT binary patch
literal 48846
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