1 /* 2 * Copyright (c) 2020, 2023, 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 24 /* 25 * @test 26 * @enablePreview 27 * @requires jdk.foreign.linker != "UNSUPPORTED" 28 * @modules java.base/jdk.internal.ref 29 * @run testng/othervm 30 * --enable-native-access=ALL-UNNAMED 31 * TestNulls 32 */ 33 34 import java.lang.foreign.*; 35 36 import jdk.internal.ref.CleanerFactory; 37 import org.testng.annotations.DataProvider; 38 import org.testng.annotations.NoInjection; 39 import org.testng.annotations.Test; 40 41 import java.lang.constant.Constable; 42 import java.lang.foreign.Arena; 43 import java.lang.invoke.MethodHandle; 44 import java.lang.invoke.MethodHandles; 45 import java.lang.invoke.MethodType; 46 import java.lang.invoke.VarHandle; 47 import java.lang.ref.Cleaner; 48 import java.lang.reflect.Array; 49 import java.lang.reflect.InvocationTargetException; 50 import java.lang.reflect.Method; 51 import java.lang.reflect.Modifier; 52 import java.nio.Buffer; 53 import java.nio.ByteBuffer; 54 import java.nio.ByteOrder; 55 import java.nio.channels.FileChannel; 56 import java.nio.charset.Charset; 57 import java.nio.file.Path; 58 import java.util.*; 59 import java.util.function.Consumer; 60 import java.util.function.Supplier; 61 import java.util.function.UnaryOperator; 62 import java.util.stream.Collectors; 63 import java.util.stream.Stream; 64 65 import static java.lang.foreign.ValueLayout.JAVA_INT; 66 import static java.lang.foreign.ValueLayout.JAVA_LONG; 67 import static org.testng.Assert.*; 68 import static org.testng.Assert.fail; 69 70 /** 71 * This test makes sure that public API classes (listed in {@link TestNulls#CLASSES}) throws NPEs whenever 72 * nulls are provided. The test looks at all the public methods in all the listed classes, and injects 73 * values automatically. If an API takes a reference, the test will try to inject nulls. For APIs taking 74 * either reference arrays, or collections, the framework will also generate additional <em>replacements</em> 75 * (e.g. other than just replacing the array, or collection with null), such as an array or collection 76 * with null elements. The test can be customized by adding/removing classes to the {@link #CLASSES} array, 77 * by adding/removing default mappings for standard carrier types (see {@link #DEFAULT_VALUES} or by 78 * adding/removing custom replacements (see {@link #REPLACEMENT_VALUES}). 79 */ 80 public class TestNulls { 81 82 static final Class<?>[] CLASSES = new Class<?>[] { 83 Arena.class, 84 MemorySegment.class, 85 MemoryLayout.class, 86 MemoryLayout.PathElement.class, 87 SequenceLayout.class, 88 ValueLayout.class, 89 ValueLayout.OfBoolean.class, 90 ValueLayout.OfByte.class, 91 ValueLayout.OfChar.class, 92 ValueLayout.OfShort.class, 93 ValueLayout.OfInt.class, 94 ValueLayout.OfFloat.class, 95 ValueLayout.OfLong.class, 96 ValueLayout.OfDouble.class, 97 AddressLayout.class, 98 PaddingLayout.class, 99 GroupLayout.class, 100 StructLayout.class, 101 UnionLayout.class, 102 Linker.class, 103 Linker.Option.class, 104 FunctionDescriptor.class, 105 SegmentAllocator.class, 106 MemorySegment.Scope.class, 107 SymbolLookup.class 108 }; 109 110 static final Set<String> EXCLUDE_LIST = Set.of( 111 "java.lang.foreign.MemorySegment/reinterpret(java.lang.foreign.Arena,java.util.function.Consumer)/1/0", 112 "java.lang.foreign.MemorySegment/reinterpret(long,java.lang.foreign.Arena,java.util.function.Consumer)/2/0" 113 ); 114 115 static final Set<String> OBJECT_METHODS = Stream.of(Object.class.getMethods()) 116 .map(Method::getName) 117 .collect(Collectors.toSet()); 118 119 static final Map<Class<?>, Object> DEFAULT_VALUES = new HashMap<>(); 120 121 static <Z> void addDefaultMapping(Class<Z> carrier, Z value) { 122 DEFAULT_VALUES.put(carrier, value); 123 } 124 125 static { 126 addDefaultMapping(char.class, (char)0); 127 addDefaultMapping(byte.class, (byte)0); 128 addDefaultMapping(short.class, (short)0); 129 addDefaultMapping(int.class, 0); 130 addDefaultMapping(float.class, 0f); 131 addDefaultMapping(long.class, 0L); 132 addDefaultMapping(double.class, 0d); 133 addDefaultMapping(boolean.class, true); 134 addDefaultMapping(ByteOrder.class, ByteOrder.nativeOrder()); 135 addDefaultMapping(Thread.class, Thread.currentThread()); 136 addDefaultMapping(Cleaner.class, CleanerFactory.cleaner()); 137 addDefaultMapping(Buffer.class, ByteBuffer.wrap(new byte[10])); 138 addDefaultMapping(ByteBuffer.class, ByteBuffer.wrap(new byte[10])); 139 addDefaultMapping(Path.class, Path.of("nonExistent")); 140 addDefaultMapping(FileChannel.MapMode.class, FileChannel.MapMode.PRIVATE); 141 addDefaultMapping(UnaryOperator.class, UnaryOperator.identity()); 142 addDefaultMapping(String.class, "Hello!"); 143 addDefaultMapping(Constable.class, "Hello!"); 144 addDefaultMapping(Class.class, String.class); 145 addDefaultMapping(Runnable.class, () -> {}); 146 addDefaultMapping(Object.class, new Object()); 147 addDefaultMapping(VarHandle.class, MethodHandles.memorySegmentViewVarHandle(JAVA_INT)); 148 addDefaultMapping(MethodHandle.class, MethodHandles.identity(int.class)); 149 addDefaultMapping(List.class, List.of()); 150 addDefaultMapping(Charset.class, Charset.defaultCharset()); 151 addDefaultMapping(Consumer.class, x -> {}); 152 addDefaultMapping(MethodType.class, MethodType.methodType(void.class)); 153 addDefaultMapping(MemoryLayout.class, ValueLayout.JAVA_INT); 154 addDefaultMapping(ValueLayout.class, ValueLayout.JAVA_INT); 155 addDefaultMapping(AddressLayout.class, ValueLayout.ADDRESS); 156 addDefaultMapping(ValueLayout.OfByte.class, ValueLayout.JAVA_BYTE); 157 addDefaultMapping(ValueLayout.OfBoolean.class, ValueLayout.JAVA_BOOLEAN); 158 addDefaultMapping(ValueLayout.OfChar.class, ValueLayout.JAVA_CHAR); 159 addDefaultMapping(ValueLayout.OfShort.class, ValueLayout.JAVA_SHORT); 160 addDefaultMapping(ValueLayout.OfInt.class, ValueLayout.JAVA_INT); 161 addDefaultMapping(ValueLayout.OfFloat.class, ValueLayout.JAVA_FLOAT); 162 addDefaultMapping(ValueLayout.OfLong.class, JAVA_LONG); 163 addDefaultMapping(ValueLayout.OfDouble.class, ValueLayout.JAVA_DOUBLE); 164 addDefaultMapping(PaddingLayout.class, MemoryLayout.paddingLayout(4)); 165 addDefaultMapping(GroupLayout.class, MemoryLayout.structLayout(ValueLayout.JAVA_INT)); 166 addDefaultMapping(StructLayout.class, MemoryLayout.structLayout(ValueLayout.JAVA_INT)); 167 addDefaultMapping(UnionLayout.class, MemoryLayout.unionLayout(ValueLayout.JAVA_INT)); 168 addDefaultMapping(SequenceLayout.class, MemoryLayout.sequenceLayout(1, ValueLayout.JAVA_INT)); 169 addDefaultMapping(SymbolLookup.class, SymbolLookup.loaderLookup()); 170 addDefaultMapping(MemorySegment.class, MemorySegment.ofArray(new byte[10])); 171 addDefaultMapping(FunctionDescriptor.class, FunctionDescriptor.ofVoid()); 172 addDefaultMapping(Linker.class, Linker.nativeLinker()); 173 addDefaultMapping(Arena.class, Arena.ofConfined()); 174 addDefaultMapping(MemorySegment.Scope.class, Arena.ofAuto().scope()); 175 addDefaultMapping(SegmentAllocator.class, SegmentAllocator.prefixAllocator(MemorySegment.ofArray(new byte[10]))); 176 addDefaultMapping(Supplier.class, () -> null); 177 addDefaultMapping(ClassLoader.class, TestNulls.class.getClassLoader()); 178 addDefaultMapping(Thread.UncaughtExceptionHandler.class, (thread, ex) -> {}); 179 } 180 181 static final Map<Class<?>, Object[]> REPLACEMENT_VALUES = new HashMap<>(); 182 183 @SafeVarargs 184 static <Z> void addReplacements(Class<Z> carrier, Z... value) { 185 REPLACEMENT_VALUES.put(carrier, value); 186 } 187 188 static { 189 addReplacements(Collection.class, null, Stream.of(new Object[] { null }).collect(Collectors.toList())); 190 addReplacements(List.class, null, Stream.of(new Object[] { null }).collect(Collectors.toList())); 191 addReplacements(Set.class, null, Stream.of(new Object[] { null }).collect(Collectors.toSet())); 192 } 193 194 @Test(dataProvider = "cases") 195 public void testNulls(String testName, @NoInjection Method meth, Object receiver, Object[] args) { 196 try { 197 meth.invoke(receiver, args); 198 fail("Method invocation completed normally"); 199 } catch (InvocationTargetException ex) { 200 Class<?> cause = ex.getCause().getClass(); 201 assertEquals(cause, NullPointerException.class, "got " + cause.getName() + " - expected NullPointerException"); 202 } catch (Throwable ex) { 203 fail("Unexpected exception: " + ex); 204 } 205 } 206 207 @DataProvider(name = "cases") 208 static Iterator<Object[]> cases() { 209 List<Object[]> cases = new ArrayList<>(); 210 for (Class<?> clazz : CLASSES) { 211 for (Method m : clazz.getMethods()) { 212 if (OBJECT_METHODS.contains(m.getName())) continue; 213 boolean isStatic = (m.getModifiers() & Modifier.STATIC) != 0; 214 List<Integer> refIndices = new ArrayList<>(); 215 for (int i = 0; i < m.getParameterCount(); i++) { 216 Class<?> param = m.getParameterTypes()[i]; 217 if (!param.isPrimitive()) { 218 refIndices.add(i); 219 } 220 } 221 for (int i : refIndices) { 222 Object[] replacements = replacements(m.getParameterTypes()[i]); 223 for (int r = 0 ; r < replacements.length ; r++) { 224 String testName = clazz.getName() + "/" + shortSig(m) + "/" + i + "/" + r; 225 if (EXCLUDE_LIST.contains(testName)) continue; 226 Object[] args = new Object[m.getParameterCount()]; 227 for (int j = 0; j < args.length; j++) { 228 args[j] = defaultValue(m.getParameterTypes()[j]); 229 } 230 args[i] = replacements[r]; 231 Object receiver = isStatic ? null : defaultValue(clazz); 232 cases.add(new Object[]{testName, m, receiver, args}); 233 } 234 } 235 } 236 } 237 return cases.iterator(); 238 }; 239 240 static String shortSig(Method m) { 241 StringJoiner sj = new StringJoiner(",", m.getName() + "(", ")"); 242 for (Class<?> parameterType : m.getParameterTypes()) { 243 sj.add(parameterType.getTypeName()); 244 } 245 return sj.toString(); 246 } 247 248 static Object defaultValue(Class<?> carrier) { 249 if (carrier.isArray()) { 250 return Array.newInstance(carrier.componentType(), 0); 251 } 252 Object value = DEFAULT_VALUES.get(carrier); 253 if (value == null) { 254 throw new UnsupportedOperationException(carrier.getName()); 255 } 256 return value; 257 } 258 259 static Object[] replacements(Class<?> carrier) { 260 if (carrier.isArray() && !carrier.getComponentType().isPrimitive()) { 261 Object arr = Array.newInstance(carrier.componentType(), 1); 262 Array.set(arr, 0, null); 263 return new Object[] { null, arr }; 264 } 265 return REPLACEMENT_VALUES.getOrDefault(carrier, new Object[] { null }); 266 } 267 }