1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.internal.foreign;
  24 
  25 import java.foreign.NativeMethodType;
  26 import java.foreign.NativeTypes;
  27 import java.foreign.Scope;
  28 import java.foreign.annotations.NativeCallback;
  29 import java.foreign.annotations.NativeGetter;
  30 import java.foreign.annotations.NativeHeader;
  31 import java.foreign.annotations.NativeStruct;
  32 import java.foreign.layout.Address;
  33 import java.foreign.layout.Function;
  34 import java.foreign.layout.Layout;
  35 import java.foreign.layout.Sequence;
  36 import java.foreign.layout.Value;
  37 import java.foreign.memory.Array;
  38 import java.foreign.memory.Callback;
  39 import java.foreign.memory.LayoutType;
  40 import java.foreign.memory.Pointer;
  41 import java.foreign.memory.Struct;
  42 import java.lang.invoke.MethodHandle;
  43 import java.lang.invoke.MethodHandles;
  44 import java.lang.invoke.MethodType;
  45 import java.lang.reflect.GenericArrayType;
  46 import java.lang.reflect.Method;
  47 import java.lang.reflect.Modifier;
  48 import java.lang.reflect.ParameterizedType;
  49 import java.lang.reflect.Type;
  50 import java.lang.reflect.TypeVariable;
  51 import java.lang.reflect.WildcardType;
  52 import java.nio.Buffer;
  53 import java.nio.ByteBuffer;
  54 import java.util.HashSet;
  55 import java.util.Optional;
  56 import java.util.Set;
  57 import java.util.function.LongFunction;
  58 import java.util.stream.Stream;
  59 import jdk.internal.foreign.memory.DescriptorParser;
  60 import jdk.internal.foreign.memory.Types;
  61 import jdk.internal.misc.Unsafe;
  62 
  63 public final class Util {
  64 
  65     private static final Unsafe UNSAFE = Unsafe.getUnsafe();
  66 
  67     public static final long BYTE_BUFFER_BASE;
  68     public static final long BUFFER_ADDRESS;
  69 
  70     static {
  71         try {
  72             BYTE_BUFFER_BASE = UNSAFE.objectFieldOffset(ByteBuffer.class.getDeclaredField("hb"));
  73             BUFFER_ADDRESS = UNSAFE.objectFieldOffset(Buffer.class.getDeclaredField("address"));
  74         }
  75         catch (Exception e) {
  76             throw new InternalError(e);
  77         }
  78     }
  79 
  80     private Util() {
  81     }
  82 
  83     public static long addUnsignedExact(long a, long b) {
  84         long result = a + b;
  85         if(Long.compareUnsigned(result, a) < 0) {
  86             throw new ArithmeticException(
  87                 "Unsigned overflow: "
  88                     + Long.toUnsignedString(a) + " + "
  89                     + Long.toUnsignedString(b));
  90         }
  91 
  92         return result;
  93     }
  94 
  95     public static Object getBufferBase(ByteBuffer bb) {
  96         return UNSAFE.getReference(bb, BYTE_BUFFER_BASE);
  97     }
  98 
  99     public static long getBufferAddress(ByteBuffer bb) {
 100         return UNSAFE.getLong(bb, BUFFER_ADDRESS);
 101     }
 102 
 103     public static long alignUp(long n, long alignment) {
 104         return (n + alignment - 1) & ~(alignment - 1);
 105     }
 106 
 107     public static boolean isCStruct(Class<?> clz) {
 108         return Struct.class.isAssignableFrom(clz) &&
 109                 clz.isAnnotationPresent(NativeStruct.class);
 110     }
 111 
 112     public static boolean isCLibrary(Class<?> clz) {
 113         return clz.isAnnotationPresent(NativeHeader.class);
 114     }
 115 
 116     public static Class<?>[] resolutionContextFor(Class<?> clz) {
 117         if (isCallback(clz)) {
 118             return clz.getAnnotation(NativeCallback.class).resolutionContext();
 119         } else if (isCStruct(clz)) {
 120             return clz.getAnnotation(NativeStruct.class).resolutionContext();
 121         } else if (isCLibrary(clz)) {
 122             return clz.getAnnotation(NativeHeader.class).resolutionContext();
 123         } else {
 124             return null;
 125         }
 126     }
 127 
 128     public static Layout variadicLayout(Class<?> c) {
 129         c = (Class<?>)unboxIfNeeded(c);
 130         if (c == char.class || c == byte.class || c == short.class || c == int.class || c == long.class) {
 131             //it is ok to approximate with a machine word here; numerics arguments in a prototype-less
 132             //function call are always rounded up to a register size anyway.
 133             return Types.INT64;
 134         } else if (c == float.class || c == double.class) {
 135             return Types.DOUBLE;
 136         } else if (Pointer.class.isAssignableFrom(c)) {
 137             return Types.POINTER;
 138         } else if (isCallback(c)) {
 139             return Types.POINTER;
 140         } else if (isCStruct(c)) {
 141             return layoutof(c);
 142         } else {
 143             throw new IllegalArgumentException("Unhandled variadic argument class: " + c);
 144         }
 145     }
 146 
 147     public static Layout layoutof(Class<?> c) {
 148         String layout;
 149         if (c.isAnnotationPresent(NativeStruct.class)) {
 150             layout = c.getAnnotation(NativeStruct.class).value();
 151         } else {
 152             throw new IllegalArgumentException("@NativeStruct or @NativeType expected: " + c);
 153         }
 154         return new DescriptorParser(layout).parseLayout();
 155     }
 156 
 157     public static Function functionof(Class<?> c) {
 158         if (! c.isAnnotationPresent(NativeCallback.class)) {
 159             throw new IllegalArgumentException("@NativeCallback expected: " + c);
 160         }
 161         NativeCallback nc = c.getAnnotation(NativeCallback.class);
 162         return new DescriptorParser(nc.value()).parseFunction();
 163     }
 164 
 165     static MethodType methodTypeFor(Method method) {
 166         return MethodType.methodType(method.getReturnType(), method.getParameterTypes());
 167     }
 168 
 169     static boolean isCompatible(Method method, Function function) {
 170         // same return kind (void or non-void)
 171         boolean isNonVoidMethod = method.getReturnType() != void.class;
 172         if (isNonVoidMethod != function.returnLayout().isPresent())  {
 173             return false;
 174         }
 175 
 176         //same vararg-ness
 177         if(method.isVarArgs() != function.isVariadic()) {
 178             return false;
 179         }
 180 
 181         //same arity (take Java varargs array into account)
 182         int expectedArity = function.argumentLayouts().size() + (function.isVariadic() ? 1 : 0);
 183         return method.getParameterCount() == expectedArity;
 184     }
 185 
 186     public static void checkCompatible(Method method, Function function) {
 187         if (!isCompatible(method, function)) {
 188             throw new IllegalArgumentException(
 189                 "Java method signature and native layout not compatible: " + method + " : " + function);
 190         }
 191     }
 192 
 193     public static boolean isCallback(Class<?> c) {
 194         return c.isAnnotationPresent(NativeCallback.class);
 195     }
 196 
 197     public static Method findFunctionalInterfaceMethod(Class<?> c) {
 198         Optional<Method> methodOpt = Optional.empty();
 199         if (c.isAnnotationPresent(NativeCallback.class)) {
 200             methodOpt = Stream.of(c.getDeclaredMethods())
 201                 .filter(m -> (m.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC)) != 0)
 202                 .findFirst();
 203         } else {
 204             throw new IllegalArgumentException("Class is not a @NativeCallback: " + c.getName());
 205         }
 206         return methodOpt.orElseThrow(IllegalStateException::new);
 207     }
 208 
 209     public static Class<?> findUniqueCallback(Class<?> cls) {
 210         Set<Class<?>> candidates = new HashSet<>();
 211         findUniqueCallbackInternal(cls, candidates);
 212         return (candidates.size() == 1) ?
 213                 candidates.iterator().next() :
 214                 null;
 215     }
 216 
 217     private static void findUniqueCallbackInternal(Class<?> cls, Set<Class<?>> candidates) {
 218         if (isCallback(cls)) {
 219             candidates.add(cls);
 220         }
 221         Class<?> sup = cls.getSuperclass();
 222         if (sup != null) {
 223             findUniqueCallbackInternal(sup, candidates);
 224         }
 225         for (Class<?> i : cls.getInterfaces()) {
 226             findUniqueCallbackInternal(i, candidates);
 227         }
 228     }
 229 
 230     @SuppressWarnings({"unchecked", "rawtypes"})
 231     public static LayoutType<?> makeType(Type carrier, Layout layout) {
 232         carrier = unboxIfNeeded(carrier);
 233         if (carrier == byte.class) {
 234             return LayoutType.ofByte(layout);
 235         } else if (carrier == void.class) {
 236             return LayoutType.ofVoid(layout);
 237         } else if (carrier == boolean.class) {
 238             return LayoutType.ofBoolean(layout);
 239         } else if (carrier == short.class) {
 240             return LayoutType.ofShort(layout);
 241         } else if (carrier == int.class) {
 242             return LayoutType.ofInt(layout);
 243         } else if (carrier == char.class) {
 244             return LayoutType.ofChar(layout);
 245         } else if (carrier == long.class) {
 246             return LayoutType.ofLong(layout);
 247         } else if (carrier == float.class) {
 248             return LayoutType.ofFloat(layout);
 249         } else if (carrier == double.class) {
 250             return LayoutType.ofDouble(layout);
 251         } else if (carrier == Pointer.class) {
 252             return NativeTypes.VOID.pointer((Address)layout);
 253         } else if (Pointer.class.isAssignableFrom(erasure(carrier))) {
 254             Type targ = extractTypeArgument(carrier);
 255             Address addr = (Address)layout;
 256             if (targ == null || addr.layout().isEmpty()) {
 257                 return NativeTypes.VOID.pointer(addr);
 258             } else {
 259                 return makeType(targ, addr.layout().get()).pointer(addr);
 260             }
 261         } else if (Array.class.isAssignableFrom(erasure(carrier))) {
 262             Type targ = extractTypeArgument(carrier);
 263             Sequence seq = (Sequence)layout;
 264             if (targ == null) {
 265                 return NativeTypes.VOID.array();
 266             } else {
 267                 return makeType(targ, seq.element()).array(seq.elementsSize());
 268             }
 269         } else if (isCStruct(erasure(carrier))) {
 270             return LayoutType.ofStruct((Class) carrier);
 271         } else if (Callback.class.isAssignableFrom(erasure(carrier))) {
 272             Type targ = extractTypeArgument(carrier);
 273             if (targ == null) {
 274                 throw new IllegalStateException("Invalid callback carrier: " + carrier.getTypeName());
 275             }
 276             return LayoutType.ofFunction((Address) layout, erasure(targ));
 277         } else {
 278             throw new IllegalStateException("Unknown carrier: " + carrier.getTypeName());
 279         }
 280     }
 281 
 282     static Type extractTypeArgument(Type t) {
 283         if (t instanceof ParameterizedType) {
 284             Type arg = ((ParameterizedType)t).getActualTypeArguments()[0];
 285             if (arg == Void.class) {
 286                 return null;
 287             } else if (arg instanceof WildcardType) {
 288                 Type[] lo =  ((WildcardType) arg).getLowerBounds();
 289                 Type[] hi =  ((WildcardType) arg).getUpperBounds();
 290                 if (lo.length == 1) {
 291                     // Lower bound is zero-elem array if this is '?' or '? extends' wildcards.
 292                     // Otherwise it's one element array.
 293                     return lo[0];
 294                 } else if (hi.length == 2 && hi[0] == Object.class) {
 295                     // Upper bound is always guaranteed to have at least one element, but
 296                     // the first bound can be j.l.Object if an interface bound is used. In that case,
 297                     // skip Object, and return the interface bound.
 298                     return hi[1];
 299                 } else if (hi.length == 1) {
 300                     // Return either the non-Object class bound, or null.
 301                     return hi[0] == Object.class ?
 302                             null : hi[0];
 303                 } else {
 304                     //unsupported combination of upper/lower bounds.
 305                     throw new IllegalStateException("Unsupported wildcard type-argument: " + arg.getTypeName());
 306                 }
 307             } else {
 308                 return arg;
 309             }
 310         } else {
 311             return null;
 312         }
 313     }
 314 
 315     public static Class<?> erasure(Type type) {
 316         if (type instanceof ParameterizedType) {
 317             return (Class<?>)((ParameterizedType)type).getRawType();
 318         } else if (type instanceof GenericArrayType) {
 319             return java.lang.reflect.Array.newInstance(erasure(((GenericArrayType)type).getGenericComponentType()), 0).getClass();
 320         } else if (type instanceof TypeVariable<?>) {
 321             return erasure(((TypeVariable<?>)type).getBounds()[0]);
 322         } else {
 323             return (Class<?>)type;
 324         }
 325     }
 326 
 327     public static Type unboxIfNeeded(Type clazz) {
 328         if (clazz == Boolean.class) {
 329             return boolean.class;
 330         } else if (clazz == Void.class) {
 331             return void.class;
 332         } else if (clazz == Byte.class) {
 333             return byte.class;
 334         } else if (clazz == Character.class) {
 335             return char.class;
 336         } else if (clazz == Short.class) {
 337             return short.class;
 338         } else if (clazz == Integer.class) {
 339             return int.class;
 340         } else if (clazz == Long.class) {
 341             return long.class;
 342         } else if (clazz == Float.class) {
 343             return float.class;
 344         } else if (clazz == Double.class) {
 345             return double.class;
 346         } else {
 347             return clazz;
 348         }
 349     }
 350 
 351     public static <Z> Pointer<Z> unsafeCast(Pointer<?> ptr, LayoutType<Z> layoutType) {
 352         return ptr.cast(NativeTypes.VOID).cast(layoutType);
 353     }
 354 
 355     public static MethodType checkNoArrays(MethodHandles.Lookup lookup, Class<?> fi) {
 356         try {
 357             return checkNoArrays(lookup.unreflect(findFunctionalInterfaceMethod(fi)).type());
 358         } catch (ReflectiveOperationException ex) {
 359             throw new IllegalStateException(ex);
 360         }
 361     }
 362     public static MethodType checkNoArrays(MethodType mt) {
 363         if (Stream.concat(Stream.of(mt.returnType()), mt.parameterList().stream())
 364                 .anyMatch(c -> Array.class.isAssignableFrom(c))) {
 365             //arrays in functions not supported!
 366             throw new UnsupportedOperationException("Array carriers not supported in functions");
 367         }
 368         return mt;
 369     }
 370 
 371     public static Pointer<?> getSyntheticCallbackAddress(Object o) {
 372         // First field
 373         return (Pointer<?>) UNSAFE.getReference(o, 0L);
 374     }
 375 
 376     public static <Z> Z withOffHeapAddress(Pointer<?> p, LongFunction<Z> longFunction) {
 377         try {
 378             try {
 379                 //address
 380                 return longFunction.apply(p.addr());
 381             } catch (UnsupportedOperationException ex) {
 382                 //heap pointer
 383                 try (Scope sc = Scope.globalScope().fork()) {
 384                     Pointer<?> offheapPtr = sc.allocate(p.type());
 385                     Pointer.copy(p, offheapPtr, p.type().bytesSize());
 386                     Z z = longFunction.apply(offheapPtr.addr());
 387                     Pointer.copy(offheapPtr, p, p.type().bytesSize());
 388                     return z;
 389                 }
 390             }
 391         } catch (Throwable ex) {
 392             throw new IllegalStateException(ex);
 393         }
 394     }
 395 
 396     public static MethodHandle getCallbackMH(Method m) {
 397         try {
 398             //Note: we need the call to setAccessible because the method to unreflect might belong to a module
 399             //that java.base does not read. However, this poses no security threat, given that the reflective method
 400             //we update here is not leaked outside (and is created insider the binder itself).
 401             m.setAccessible(true);
 402             MethodHandle mh = MethodHandles.lookup().unreflect(m);
 403             Util.checkNoArrays(mh.type());
 404             return mh;
 405         } catch (Throwable ex) {
 406             throw new IllegalStateException(ex);
 407         }
 408     }
 409 
 410     public static Function getResolvedFunction(Class<?> nativeCallback, Method m) {
 411         LayoutResolver resolver = LayoutResolver.get(nativeCallback);
 412         return resolver.resolve(Util.functionof(nativeCallback));
 413     }
 414 
 415     public static NativeMethodType nativeMethodType(Function function, Method method) {
 416         checkCompatible(method, function);
 417 
 418         LayoutType<?> ret = function.returnLayout()
 419                 .<LayoutType<?>>map(l -> makeType(method.getGenericReturnType(), l))
 420                 .orElse(NativeTypes.VOID);
 421 
 422         // Use function argument size and ignore last argument from method for vararg function
 423         LayoutType<?>[] args = new LayoutType<?>[function.argumentLayouts().size()];
 424         for (int i = 0; i < args.length; i++) {
 425             args[i] = makeType(method.getGenericParameterTypes()[i], function.argumentLayouts().get(i));
 426         }
 427 
 428         return NativeMethodType.of(function.isVariadic(), ret, args);
 429     }
 430 
 431     @SuppressWarnings("unchecked")
 432     public static Class<?> findStructInterface(Struct<?> struct) {
 433         for (Class<?> intf : struct.getClass().getInterfaces()) {
 434             if (intf.isAnnotationPresent(NativeStruct.class)) {
 435                 return intf;
 436             }
 437         }
 438         throw new IllegalStateException("Can not find struct interface");
 439     }
 440 
 441 
 442     public static Method getterByName(Class<?> cls, String name) {
 443         for (Method m : cls.getDeclaredMethods()) {
 444             NativeGetter ng = m.getAnnotation(NativeGetter.class);
 445             if (ng != null && ng.value().equals(name)) {
 446                 return m;
 447             }
 448         }
 449         return null;
 450     }
 451 
 452     public static Layout requireNoEndianLayout(Layout layout) {
 453         if (layout instanceof Value) {
 454             if (!((Value)layout).isNativeByteOrder()) {
 455                 throw new IllegalArgumentException("Non-platform endianess not allowed in method argument/return value");
 456             }
 457         }
 458         return layout;
 459     }
 460 
 461     public static long unsafeArrayBase(Class<?> arrayClass) {
 462         return UNSAFE.arrayBaseOffset(arrayClass);
 463     }
 464 
 465     public static long unsafeArrayScale(Class<?> arrayClass) {
 466         return UNSAFE.arrayIndexScale(arrayClass);
 467     }
 468 
 469     public static int sizeof(Class<?> carrier) {
 470         if (carrier == byte.class || carrier == boolean.class) {
 471             return 8;
 472         } else if (carrier == char.class || carrier == short.class) {
 473             return 16;
 474         } else if (carrier == int.class || carrier == float.class) {
 475             return 32;
 476         } else if (carrier == long.class || carrier == double.class) {
 477             return 64;
 478         } else {
 479             throw new IllegalStateException("Unexpected carrier: " + carrier.getName());
 480         }
 481     }
 482 }