You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sd...@apache.org on 2022/01/19 12:04:04 UTC

[ignite-3] branch main updated: IGNITE-16258 Support Serializable lambdas marshalling in User Object Serialization

This is an automated email from the ASF dual-hosted git repository.

sdanilov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 4f8f7ae  IGNITE-16258 Support Serializable lambdas marshalling in User Object Serialization
4f8f7ae is described below

commit 4f8f7aef8b475381cd23bc2865a3986472679d40
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Wed Jan 19 16:03:56 2022 +0400

    IGNITE-16258 Support Serializable lambdas marshalling in User Object Serialization
---
 .../network/serialization/BuiltInType.java         |   3 +-
 .../SpecialSerializationMethodsImpl.java           |  94 ++++++++---------
 .../marshal/BuiltInContainerMarshallers.java       |   2 +-
 .../serialization/marshal/BuiltInMarshalling.java  |  44 ++++----
 .../marshal/BuiltInNonContainerMarshallers.java    |   1 +
 .../network/serialization/marshal/Classes.java}    |  30 +++++-
 .../marshal/DefaultUserObjectMarshaller.java       |   8 +-
 .../marshal/MarshallingNotSupportedException.java} |  11 +-
 ...iptorsTest.java => BuiltInDescriptorsTest.java} |   6 +-
 .../network/serialization/marshal/ClassesTest.java | 101 ++++++++++++++++++
 ...erObjectMarshallerWithArbitraryObjectsTest.java | 116 +++++++++++----------
 ...efaultUserObjectMarshallerWithBuiltinsTest.java |  10 +-
 .../marshal/NoArgConstructorInstantiationTest.java |   2 +-
 ...=> NonSerializableWithoutNoArgConstructor.java} |   4 +-
 .../marshal/UnsafeInstantiationTest.java           |   4 +-
 15 files changed, 286 insertions(+), 150 deletions(-)

diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltInType.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltInType.java
index 56f6f2b..e0a5de8 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltInType.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BuiltInType.java
@@ -79,7 +79,8 @@ public enum BuiltInType {
     LINKED_HASH_MAP(41, LinkedHashMap.class),
     BIT_SET(42, BitSet.class),
     NULL(43, Null.class),
-    REFERENCE(44, DummyPlaceholder.class)
+    REFERENCE(44, DummyPlaceholder.class),
+    CLASS(45, Class.class)
     ;
 
     /**
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java
index 84e7caa..aba7b96 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SpecialSerializationMethodsImpl.java
@@ -19,31 +19,29 @@ package org.apache.ignite.internal.network.serialization;
 
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
 import java.util.Objects;
 import org.jetbrains.annotations.Nullable;
 
 /**
- * Encapsulates special serialization methods like writeReplace()/readResolve() for convenient invocation.
+ * Encapsulates special serialization methods like writeReplace()/readResolve() and so on for convenient invocation.
  */
 class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
-    /** MethodHandle that can be used to invoke writeReplace() on the target class. */
+    /** Method that can be used to invoke writeReplace() on the target class. */
     @Nullable
-    private final MethodHandle writeReplaceHandle;
+    private final Method writeReplace;
 
-    /** MethodHandle that can be used to invoke readResolve() on the target class. */
+    /** Method that can be used to invoke readResolve() on the target class. */
     @Nullable
-    private final MethodHandle readResolveHandle;
+    private final Method readResolve;
 
-    /** MethodHandle that can be used to invoke writeObject() on the target class. */
+    /** Method that can be used to invoke writeObject() on the target class. */
     @Nullable
-    private final MethodHandle writeObjectHandle;
+    private final Method writeObject;
 
-    /** MethodHandle that can be used to invoke readObject() on the target class. */
+    /** Method that can be used to invoke readObject() on the target class. */
     @Nullable
-    private final MethodHandle readObjectHandle;
+    private final Method readObject;
 
     /**
      * Creates a new instance from the provided descriptor.
@@ -51,47 +49,47 @@ class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
      * @param descriptor class descriptor on which class to operate
      */
     public SpecialSerializationMethodsImpl(ClassDescriptor descriptor) {
-        writeReplaceHandle = descriptor.hasWriteReplace() ? writeReplaceHandle(descriptor) : null;
-        readResolveHandle = descriptor.hasReadResolve() ? readResolveHandle(descriptor) : null;
-        writeObjectHandle = descriptor.hasWriteObject() ? writeObjectHandle(descriptor) : null;
-        readObjectHandle = descriptor.hasReadObject() ? readObjectHandle(descriptor) : null;
+        writeReplace = descriptor.hasWriteReplace() ? writeReplaceInvoker(descriptor) : null;
+        readResolve = descriptor.hasReadResolve() ? readResolveInvoker(descriptor) : null;
+        writeObject = descriptor.hasWriteObject() ? writeObjectInvoker(descriptor) : null;
+        readObject = descriptor.hasReadObject() ? readObjectInvoker(descriptor) : null;
     }
 
-    private static MethodHandle writeReplaceHandle(ClassDescriptor descriptor) {
+    private static Method writeReplaceInvoker(ClassDescriptor descriptor) {
         try {
-            return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
-                    .findVirtual(descriptor.clazz(), "writeReplace", MethodType.methodType(Object.class))
-                    .asType(MethodType.methodType(Object.class, Object.class));
+            Method method = descriptor.clazz().getDeclaredMethod("writeReplace");
+            method.setAccessible(true);
+            return method;
         } catch (ReflectiveOperationException e) {
             throw new ReflectionException("Cannot find writeReplace() in " + descriptor.clazz(), e);
         }
     }
 
-    private static MethodHandle readResolveHandle(ClassDescriptor descriptor) {
+    private static Method readResolveInvoker(ClassDescriptor descriptor) {
         try {
-            return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
-                    .findVirtual(descriptor.clazz(), "readResolve", MethodType.methodType(Object.class))
-                    .asType(MethodType.methodType(Object.class, Object.class));
+            Method method = descriptor.clazz().getDeclaredMethod("readResolve");
+            method.setAccessible(true);
+            return method;
         } catch (ReflectiveOperationException e) {
             throw new ReflectionException("Cannot find readResolve() in " + descriptor.clazz(), e);
         }
     }
 
-    private static MethodHandle writeObjectHandle(ClassDescriptor descriptor) {
+    private static Method writeObjectInvoker(ClassDescriptor descriptor) {
         try {
-            return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
-                    .findVirtual(descriptor.clazz(), "writeObject", MethodType.methodType(void.class, ObjectOutputStream.class))
-                    .asType(MethodType.methodType(void.class, Object.class, ObjectOutputStream.class));
+            Method method = descriptor.clazz().getDeclaredMethod("writeObject", ObjectOutputStream.class);
+            method.setAccessible(true);
+            return method;
         } catch (ReflectiveOperationException e) {
             throw new ReflectionException("Cannot find writeObject() in " + descriptor.clazz(), e);
         }
     }
 
-    private static MethodHandle readObjectHandle(ClassDescriptor descriptor) {
+    private static Method readObjectInvoker(ClassDescriptor descriptor) {
         try {
-            return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
-                    .findVirtual(descriptor.clazz(), "readObject", MethodType.methodType(void.class, ObjectInputStream.class))
-                    .asType(MethodType.methodType(void.class, Object.class, ObjectInputStream.class));
+            Method method = descriptor.clazz().getDeclaredMethod("readObject", ObjectInputStream.class);
+            method.setAccessible(true);
+            return method;
         } catch (ReflectiveOperationException e) {
             throw new ReflectionException("Cannot find readObject() in " + descriptor.clazz(), e);
         }
@@ -100,13 +98,11 @@ class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
     /** {@inheritDoc} */
     @Override
     public Object writeReplace(Object object) throws SpecialMethodInvocationException {
-        Objects.requireNonNull(writeReplaceHandle);
+        Objects.requireNonNull(writeReplace);
 
         try {
-            return writeReplaceHandle.invokeExact(object);
-        } catch (Error e) {
-            throw e;
-        } catch (Throwable e) {
+            return writeReplace.invoke(object, (Object[]) null);
+        } catch (ReflectiveOperationException e) {
             throw new SpecialMethodInvocationException("writeReplace() invocation failed on " + object, e);
         }
     }
@@ -114,13 +110,11 @@ class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
     /** {@inheritDoc} */
     @Override
     public Object readResolve(Object object) throws SpecialMethodInvocationException {
-        Objects.requireNonNull(readResolveHandle);
+        Objects.requireNonNull(readResolve);
 
         try {
-            return readResolveHandle.invokeExact(object);
-        } catch (Error e) {
-            throw e;
-        } catch (Throwable e) {
+            return readResolve.invoke(object, (Object[]) null);
+        } catch (ReflectiveOperationException e) {
             throw new SpecialMethodInvocationException("readResolve() invocation failed on " + object, e);
         }
     }
@@ -128,13 +122,11 @@ class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
     /** {@inheritDoc} */
     @Override
     public void writeObject(Object object, ObjectOutputStream stream) throws SpecialMethodInvocationException {
-        Objects.requireNonNull(writeObjectHandle);
+        Objects.requireNonNull(writeObject);
 
         try {
-            writeObjectHandle.invokeExact(object, stream);
-        } catch (Error e) {
-            throw e;
-        } catch (Throwable e) {
+            writeObject.invoke(object, stream);
+        } catch (ReflectiveOperationException e) {
             throw new SpecialMethodInvocationException("writeObject() invocation failed on " + object, e);
         }
     }
@@ -142,13 +134,11 @@ class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
     /** {@inheritDoc} */
     @Override
     public void readObject(Object object, ObjectInputStream stream) throws SpecialMethodInvocationException {
-        Objects.requireNonNull(readObjectHandle);
+        Objects.requireNonNull(readObject);
 
         try {
-            readObjectHandle.invokeExact(object, stream);
-        } catch (Error e) {
-            throw e;
-        } catch (Throwable e) {
+            readObject.invoke(object, stream);
+        } catch (ReflectiveOperationException e) {
             throw new SpecialMethodInvocationException("readObject() invocation failed on " + object, e);
         }
     }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
index 1918984..13a2eb6 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInContainerMarshallers.java
@@ -83,7 +83,7 @@ class BuiltInContainerMarshallers {
 
     <T> void fillGenericRefArray(DataInputStream input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
             throws IOException, UnmarshalException {
-        BuiltInMarshalling.fillGenericRefArray(input, array, elementReader, context);
+        BuiltInMarshalling.fillGenericRefArrayFrom(input, array, elementReader, context);
     }
 
     void writeBuiltInCollection(Collection<?> object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
index 787c66c..b981142 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInMarshalling.java
@@ -229,11 +229,11 @@ class BuiltInMarshalling {
     }
 
     static void writeBigDecimal(BigDecimal object, DataOutput output) throws IOException {
-        output.writeUTF(object.toString());
+        writeString(object.toString(), output);
     }
 
     static BigDecimal readBigDecimal(DataInput input) throws IOException {
-        return new BigDecimal(input.readUTF());
+        return new BigDecimal(readString(input));
     }
 
     static void writeEnum(Enum<?> object, DataOutput output) throws IOException {
@@ -245,32 +245,37 @@ class BuiltInMarshalling {
 
         assert enumClass.getSuperclass() == Enum.class;
 
-        output.writeUTF(enumClass.getName());
-        output.writeUTF(object.name());
+        writeClass(enumClass, output);
+        writeString(object.name(), output);
     }
 
+    @SuppressWarnings("unchecked")
     static <T extends Enum<T>> Enum<?> readEnum(DataInput input) throws IOException, UnmarshalException {
-        String enumClassName = input.readUTF();
-        Class<T> enumClass = enumClass(enumClassName);
+        Class<T> enumClass = (Class<T>) readClass(input);
         return Enum.valueOf(enumClass, input.readUTF());
     }
 
-    private static <T extends Enum<T>> Class<T> enumClass(String className) throws UnmarshalException {
-        return classByName(className, "enum");
-    }
-
     @NotNull
-    private static <T> Class<T> classByName(String className, String classKind) throws UnmarshalException {
+    private static <T> Class<T> classByName(String className) throws UnmarshalException {
         try {
             // TODO: what classloader to use?
             @SuppressWarnings("unchecked") Class<T> castedClass = (Class<T>) Class.forName(className);
             return castedClass;
         } catch (ClassNotFoundException e) {
-            throw new UnmarshalException("Can not load " + classKind + " class: " + className, e);
+            throw new UnmarshalException("Can not load a class: " + className, e);
         }
     }
 
-    static <T> void writeRefArray(T[] array, DataOutputStream output, ValueWriter<T> valueWriter, MarshallingContext context)
+    static void writeClass(Class<?> classToWrite, DataOutput output) throws IOException {
+        writeString(classToWrite.getName(), output);
+    }
+
+    static Class<?> readClass(DataInput input) throws IOException, UnmarshalException {
+        String className = readString(input);
+        return classByName(className);
+    }
+
+    private static <T> void writeRefArray(T[] array, DataOutputStream output, ValueWriter<T> valueWriter, MarshallingContext context)
             throws IOException, MarshalException {
         writeLength(array.length, output);
         for (T object : array) {
@@ -278,7 +283,7 @@ class BuiltInMarshalling {
         }
     }
 
-    static <T> T[] readRefArray(
+    private static <T> T[] readRefArray(
             DataInputStream input,
             IntFunction<T[]> arrayFactory,
             ValueReader<T> valueReader,
@@ -301,8 +306,7 @@ class BuiltInMarshalling {
 
     @SuppressWarnings("unchecked")
     private static <T> IntFunction<T[]> readTypeAndCreateArrayFactory(DataInput input) throws IOException, UnmarshalException {
-        String componentClassName = input.readUTF();
-        Class<T> componentType = classByName(componentClassName, "component");
+        Class<T> componentType = (Class<T>) readClass(input);
         return len -> (T[]) Array.newInstance(componentType, len);
     }
 
@@ -312,7 +316,7 @@ class BuiltInMarshalling {
         return arrayFactory.apply(length);
     }
 
-    static <T> void fillGenericRefArray(DataInputStream input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
+    static <T> void fillGenericRefArrayFrom(DataInputStream input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
             throws IOException, UnmarshalException {
         fillRefArrayFrom(input, array, elementReader, context);
     }
@@ -335,13 +339,13 @@ class BuiltInMarshalling {
     }
 
     static void writeEnumArray(Enum<?>[] array, DataOutputStream output, MarshallingContext context) throws IOException, MarshalException {
-        output.writeUTF(array.getClass().getComponentType().getName());
+        writeClass(array.getClass().getComponentType(), output);
         writeRefArray(array, output, enumWriter, context);
     }
 
+    @SuppressWarnings("unchecked")
     static Enum<?>[] readEnumArray(DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException {
-        String enumClassName = input.readUTF();
-        Class<? extends Enum<?>> enumClass = enumClass(enumClassName);
+        Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) readClass(input);
         return readRefArray(input, len -> (Enum<?>[]) Array.newInstance(enumClass, len), enumReader, context);
     }
 
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java
index a3b8769..e6a0a15 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/BuiltInNonContainerMarshallers.java
@@ -69,6 +69,7 @@ class BuiltInNonContainerMarshallers {
         addSingle(map, Enum[].class, BuiltInMarshalling::writeEnumArray, BuiltInMarshalling::readEnumArray);
         addSingle(map, BitSet.class, BuiltInMarshalling::writeBitSet, BuiltInMarshalling::readBitSet);
         addSingle(map, Null.class, (obj, output) -> {}, input -> null);
+        addSingle(map, Class.class, BuiltInMarshalling::writeClass, BuiltInMarshalling::readClass);
 
         return Map.copyOf(map);
     }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/Classes.java
similarity index 52%
copy from modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java
copy to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/Classes.java
index 0516176..e5fd895 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/Classes.java
@@ -17,10 +17,32 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-class WithoutNoArgConstructor {
-    int value;
+import java.io.Serializable;
 
-    public WithoutNoArgConstructor(int value) {
-        this.value = value;
+/**
+ * Utilities to work with classes.
+ */
+class Classes {
+    static boolean isSerializable(Class<?> objectClass) {
+        return Serializable.class.isAssignableFrom(objectClass);
+    }
+
+    static boolean isLambda(Class<?> objectClass) {
+        return !objectClass.isPrimitive() && !objectClass.isArray()
+                && !objectClass.isAnonymousClass() && !objectClass.isLocalClass()
+                && objectClass.isSynthetic()
+                && classCannotBeLoadedByName(objectClass);
+    }
+
+    private static boolean classCannotBeLoadedByName(Class<?> objectClass) {
+        try {
+            Class.forName(objectClass.getName());
+            return false;
+        } catch (ClassNotFoundException e) {
+            return true;
+        }
+    }
+
+    private Classes() {
     }
 }
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index b79c1ac..20c7d9a 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -134,10 +134,14 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller {
 
         Class<?> objectClass = object.getClass();
         if (isInnerClass(objectClass)) {
-            throw new IllegalArgumentException("Non-static inner class instances are not supported for marshalling: " + objectClass);
+            throw new MarshallingNotSupportedException("Non-static inner class instances are not supported for marshalling: "
+                    + objectClass);
         }
         if (isCapturingClosure(objectClass)) {
-            throw new IllegalArgumentException("Capturing nested class instances are not supported for marshalling: " + object);
+            throw new MarshallingNotSupportedException("Capturing nested class instances are not supported for marshalling: " + object);
+        }
+        if (Classes.isLambda(objectClass) && !Classes.isSerializable(objectClass)) {
+            throw new MarshallingNotSupportedException("Non-serializable lambda instances are not supported for marshalling: " + object);
         }
     }
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingNotSupportedException.java
similarity index 74%
copy from modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java
copy to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingNotSupportedException.java
index 0516176..94b9f2d 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/MarshallingNotSupportedException.java
@@ -17,10 +17,13 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-class WithoutNoArgConstructor {
-    int value;
+import org.apache.ignite.lang.IgniteException;
 
-    public WithoutNoArgConstructor(int value) {
-        this.value = value;
+/**
+ * Thrown to indicate that an object cannot be marshalled, or that it will be impossible to unmarshal it then.
+ */
+class MarshallingNotSupportedException extends IgniteException {
+    public MarshallingNotSupportedException(String msg) {
+        super(msg);
     }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/BuiltInDescriptorsTest.java
similarity index 97%
rename from modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java
rename to modules/network/src/test/java/org/apache/ignite/internal/network/serialization/BuiltInDescriptorsTest.java
index 0a29655..671e452 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/DefaultDescriptorsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/BuiltInDescriptorsTest.java
@@ -29,6 +29,7 @@ import static org.apache.ignite.internal.network.serialization.BuiltInType.BYTE_
 import static org.apache.ignite.internal.network.serialization.BuiltInType.CHAR;
 import static org.apache.ignite.internal.network.serialization.BuiltInType.CHAR_ARRAY;
 import static org.apache.ignite.internal.network.serialization.BuiltInType.CHAR_BOXED;
+import static org.apache.ignite.internal.network.serialization.BuiltInType.CLASS;
 import static org.apache.ignite.internal.network.serialization.BuiltInType.DATE;
 import static org.apache.ignite.internal.network.serialization.BuiltInType.DECIMAL;
 import static org.apache.ignite.internal.network.serialization.BuiltInType.DECIMAL_ARRAY;
@@ -67,9 +68,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import org.junit.jupiter.api.Test;
 
 /**
- * Tests for default descriptors.
+ * Tests for built-in descriptors.
  */
-public class DefaultDescriptorsTest {
+public class BuiltInDescriptorsTest {
     /**
      * Tests that default descriptor were not changed by accident.
      */
@@ -120,5 +121,6 @@ public class DefaultDescriptorsTest {
         assertEquals(42, BIT_SET.descriptorId());
         assertEquals(43, NULL.descriptorId());
         assertEquals(44, REFERENCE.descriptorId());
+        assertEquals(45, CLASS.descriptorId());
     }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/ClassesTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/ClassesTest.java
new file mode 100644
index 0000000..09be2fc
--- /dev/null
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/ClassesTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.network.serialization.marshal;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.Serializable;
+import org.junit.jupiter.api.Test;
+
+class ClassesTest {
+    @Test
+    void isSerializableReturnsFalseForNonSerializableClass() {
+        assertFalse(Classes.isSerializable(NonSerializable.class));
+    }
+
+    @Test
+    void isSerializableReturnsTrueForSerializableClass() {
+        assertTrue(Classes.isSerializable(EmptySerializable.class));
+    }
+
+    @Test
+    void isLambdaReturnsFalseForOrdinaryClassInstance() {
+        assertFalse(Classes.isLambda(NonSerializable.class));
+    }
+
+    @SuppressWarnings("Convert2Lambda")
+    @Test
+    void isLambdaReturnsFalseForAnonymousClassInstance() {
+        Runnable object = new Runnable() {
+            @Override
+            public void run() {
+                // no-op
+            }
+        };
+
+        assertFalse(Classes.isLambda(object.getClass()));
+    }
+
+    @Test
+    void isLambdaReturnsTrueForNonSerializableLambda() {
+        Runnable object = () -> {};
+
+        assertTrue(Classes.isLambda(object.getClass()));
+    }
+
+    @Test
+    void isLambdaReturnsTrueForSerializableLambda() {
+        Runnable object = serializableLambda();
+
+        assertTrue(Classes.isLambda(object.getClass()));
+    }
+
+    private Runnable serializableLambda() {
+        return (Runnable & Serializable) () -> {};
+    }
+
+    @Test
+    void isLambdaReturnsFalseForPrimitiveClasses() {
+        assertFalse(Classes.isLambda(int.class));
+    }
+
+    @Test
+    void isLambdaReturnsFalseForPrimitiveArrayClasses() {
+        assertFalse(Classes.isLambda(int[].class));
+    }
+
+    @Test
+    void isLambdaReturnsFalseForObjectArrayClasses() {
+        assertFalse(Classes.isLambda(Object[].class));
+    }
+
+    @Test
+    void isLambdaReturnsFalseForEnumClasses() {
+        assertFalse(Classes.isLambda(EmptyEnum.class));
+    }
+
+    private static class NonSerializable {
+    }
+
+    private static class EmptySerializable implements Serializable {
+    }
+
+    private enum EmptyEnum {
+    }
+}
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
index 3239988..6a89e9b 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithArbitraryObjectsTest.java
@@ -30,7 +30,6 @@ import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.sameInstance;
-import static org.hamcrest.Matchers.startsWith;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
@@ -49,7 +48,6 @@ import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
 import org.apache.ignite.internal.network.serialization.ClassDescriptorRegistry;
 import org.apache.ignite.internal.network.serialization.IdIndexedDescriptors;
 import org.jetbrains.annotations.NotNull;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -192,6 +190,18 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     }
 
     @Test
+    void doesNotSupportInnerClassInstances() {
+        assertThrows(MarshallingNotSupportedException.class, () -> marshaller.marshal(new Inner()));
+    }
+
+    @Test
+    void doesNotSupportInnerClassInstancesInsideContainers() {
+        List<Inner> list = singletonList(new Inner());
+
+        assertThrows(MarshallingNotSupportedException.class, () -> marshaller.marshal(list));
+    }
+
+    @Test
     void supportsNonCapturingAnonymousClassInstances() throws Exception {
         Callable<String> unmarshalled = marshalAndUnmarshalNonNull(nonCapturingAnonymousInstance());
 
@@ -209,89 +219,84 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     }
 
     @Test
-    void supportsNonCapturingLambdas() throws Exception {
-        Callable<String> unmarshalled = marshalAndUnmarshalNonNull(nonCapturingLambda());
+    void doesNotSupportCapturingAnonymousClassInstances() {
+        Runnable capturingClosure = capturingAnonymousInstance();
 
-        assertThat(unmarshalled.call(), is("Hi!"));
+        assertThrows(MarshallingNotSupportedException.class, () -> marshaller.marshal(capturingClosure));
     }
 
-    private static Callable<String> nonCapturingLambda() {
-        return () -> "Hi!";
+    private Runnable capturingAnonymousInstance() {
+        //noinspection Convert2Lambda
+        return new Runnable() {
+            @Override
+            public void run() {
+                System.out.println(DefaultUserObjectMarshallerWithArbitraryObjectsTest.this);
+            }
+        };
     }
 
     @Test
-    @Disabled("IGNITE-16258")
-    // TODO: IGNITE-16258 - enable this test when we are able to work with serializable lambdas
-    void supportsNonCapturingSerializableLambdas() throws Exception {
-        Callable<String> unmarshalled = marshalAndUnmarshalNonNull(nonCapturingSerializableLambda());
-
-        assertThat(unmarshalled.call(), is("Hi!"));
-    }
+    void doesNotSupportCapturingAnonymousClassInstancesInsideContainers() {
+        Runnable capturingAnonymousInstance = capturingAnonymousInstance();
+        List<Runnable> list = singletonList(capturingAnonymousInstance);
 
-    private static Callable<String> nonCapturingSerializableLambda() {
-        return (Callable<String> & Serializable) () -> "Hi!";
+        assertThrows(MarshallingNotSupportedException.class, () -> marshaller.marshal(list));
     }
 
+    /**
+     * We should not support non-capturing non-serializable Lambdas. Even though it's possible to marshal and unmarshal
+     * such lambda inside the same JVM, it's impossible to load its class by name (even when it exists in the JVM with
+     * that same name!), so such lambdas will be impossible to unmarshal at another JVM.
+     *
+     */
     @Test
-    void doesNotSupportInnerClassInstances() {
-        Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(new Inner()));
-        assertThat(ex.getMessage(), is("Non-static inner class instances are not supported for marshalling: " + Inner.class));
+    void doesNotSupportNonCapturingNonSerializableLambdas() {
+        assertThrows(MarshallingNotSupportedException.class, () -> marshalAndUnmarshalNonNull(nonCapturingLambda()));
     }
 
-    @Test
-    void doesNotSupportInnerClassInstancesInsideContainers() {
-        List<Inner> list = singletonList(new Inner());
-
-        Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(list));
-        assertThat(ex.getMessage(), is("Non-static inner class instances are not supported for marshalling: " + Inner.class));
+    private static Callable<String> nonCapturingLambda() {
+        return () -> "Hi!";
     }
 
     @Test
-    void doesNotSupportCapturingAnonymousClassInstances() {
-        Runnable capturingClosure = capturingAnonymousInstance();
+    void supportsNonCapturingSerializableLambdas() throws Exception {
+        Callable<String> unmarshalled = marshalAndUnmarshalNonNull(nonCapturingSerializableLambda());
 
-        Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(capturingClosure));
-        assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+        assertThat(unmarshalled.call(), is("Hi!"));
     }
 
-    private Runnable capturingAnonymousInstance() {
-        //noinspection Convert2Lambda
-        return new Runnable() {
-            @Override
-            public void run() {
-                System.out.println(DefaultUserObjectMarshallerWithArbitraryObjectsTest.this);
-            }
-        };
+    private static Callable<String> nonCapturingSerializableLambda() {
+        return (Callable<String> & Serializable) () -> "Hi!";
     }
 
     @Test
-    void doesNotSupportCapturingAnonymousClassInstancesInsideContainers() {
-        Runnable capturingAnonymousInstance = capturingAnonymousInstance();
-        List<Runnable> list = singletonList(capturingAnonymousInstance);
+    void doesNotSupportCapturingNonSerializableLambdas() {
+        Runnable capturingClosure = capturingNonSerializableLambda();
 
-        Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(list));
-        assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+        assertThrows(MarshallingNotSupportedException.class, () -> marshaller.marshal(capturingClosure));
+    }
+
+    private Runnable capturingNonSerializableLambda() {
+        return () -> System.out.println(DefaultUserObjectMarshallerWithArbitraryObjectsTest.this);
     }
 
     @Test
-    void doesNotSupportCapturingLambdas() {
-        Runnable capturingClosure = capturingLambda();
+    void doesNotSupportCapturingSerializableLambdas() {
+        Runnable capturingClosure = capturingSerializableLambda();
 
-        Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(capturingClosure));
-        assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+        assertThrows(MarshallingNotSupportedException.class, () -> marshaller.marshal(capturingClosure));
     }
 
-    private Runnable capturingLambda() {
-        return () -> System.out.println(DefaultUserObjectMarshallerWithArbitraryObjectsTest.this);
+    private Runnable capturingSerializableLambda() {
+        return (Runnable & Serializable) () -> System.out.println(DefaultUserObjectMarshallerWithArbitraryObjectsTest.this);
     }
 
     @Test
-    void doesNotSupportCapturingAnonymousLambdasInsideContainers() {
-        Runnable capturingLambda = capturingLambda();
+    void doesNotSupportCapturingLambdasInsideContainers() {
+        Runnable capturingLambda = capturingNonSerializableLambda();
         List<Runnable> list = singletonList(capturingLambda);
 
-        Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(list));
-        assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+        assertThrows(MarshallingNotSupportedException.class, () -> marshaller.marshal(list));
     }
 
     @Test
@@ -317,8 +322,7 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     void doesNotSupportCapturingLocalClassInstances() {
         Object instance = capturingLocalClassInstance();
 
-        Throwable ex = assertThrows(IllegalArgumentException.class, () -> marshaller.marshal(instance));
-        assertThat(ex.getMessage(), startsWith("Capturing nested class instances are not supported for marshalling: "));
+        assertThrows(MarshallingNotSupportedException.class, () -> marshaller.marshal(instance));
     }
 
     private Object capturingLocalClassInstance() {
@@ -329,8 +333,8 @@ class DefaultUserObjectMarshallerWithArbitraryObjectsTest {
     }
 
     @Test
-    void supportsClassesWithoutNoArgConstructor() throws Exception {
-        WithoutNoArgConstructor unmarshalled = marshalAndUnmarshalNonNull(new WithoutNoArgConstructor(42));
+    void supportsNonSerializableClassesWithoutNoArgConstructor() throws Exception {
+        NonSerializableWithoutNoArgConstructor unmarshalled = marshalAndUnmarshalNonNull(new NonSerializableWithoutNoArgConstructor(42));
 
         assertThat(unmarshalled.value, is(42));
     }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
index 151e19a..4577b9c 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithBuiltinsTest.java
@@ -203,15 +203,19 @@ class DefaultUserObjectMarshallerWithBuiltinsTest {
                 builtInTypeValue(new Enum[]{SimpleEnum.FIRST, SimpleEnum.SECOND}, Enum[].class, BuiltInType.ENUM_ARRAY),
                 builtInTypeValue(new SimpleEnum[]{SimpleEnum.FIRST, SimpleEnum.SECOND}, SimpleEnum[].class, BuiltInType.ENUM_ARRAY),
                 builtInTypeValue(EnumWithAnonClassesForMembers.FIRST, EnumWithAnonClassesForMembers.class, BuiltInType.ENUM),
-                builtInTypeValue(new Enum[]{EnumWithAnonClassesForMembers.FIRST, EnumWithAnonClassesForMembers.SECOND}, Enum[].class,
-                        BuiltInType.ENUM_ARRAY),
+                builtInTypeValue(
+                        new Enum[]{EnumWithAnonClassesForMembers.FIRST, EnumWithAnonClassesForMembers.SECOND},
+                        Enum[].class,
+                        BuiltInType.ENUM_ARRAY
+                ),
                 builtInTypeValue(
                         new EnumWithAnonClassesForMembers[]{EnumWithAnonClassesForMembers.FIRST, EnumWithAnonClassesForMembers.SECOND},
                         EnumWithAnonClassesForMembers[].class,
                         BuiltInType.ENUM_ARRAY
                 ),
                 builtInTypeValue(BitSet.valueOf(new long[]{42, 43}), BitSet.class, BuiltInType.BIT_SET),
-                builtInTypeValue(null, Null.class, BuiltInType.NULL)
+                builtInTypeValue(null, Null.class, BuiltInType.NULL),
+                builtInTypeValue(IntHolder.class, Class.class, BuiltInType.CLASS)
         ).map(Arguments::of);
     }
 
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiationTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiationTest.java
index 3357f13..693a276 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiationTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NoArgConstructorInstantiationTest.java
@@ -43,7 +43,7 @@ class NoArgConstructorInstantiationTest {
 
     @Test
     void doesNotSupportClassesWithoutNoArgConstructor() {
-        assertFalse(instantiation.supports(WithoutNoArgConstructor.class));
+        assertFalse(instantiation.supports(NonSerializableWithoutNoArgConstructor.class));
     }
 
     @Test
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NonSerializableWithoutNoArgConstructor.java
similarity index 89%
rename from modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java
rename to modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NonSerializableWithoutNoArgConstructor.java
index 0516176..f6bbb80 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/WithoutNoArgConstructor.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/NonSerializableWithoutNoArgConstructor.java
@@ -17,10 +17,10 @@
 
 package org.apache.ignite.internal.network.serialization.marshal;
 
-class WithoutNoArgConstructor {
+class NonSerializableWithoutNoArgConstructor {
     int value;
 
-    public WithoutNoArgConstructor(int value) {
+    public NonSerializableWithoutNoArgConstructor(int value) {
         this.value = value;
     }
 }
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiationTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiationTest.java
index 7beb536..a45ddbd 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiationTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/UnsafeInstantiationTest.java
@@ -34,7 +34,7 @@ class UnsafeInstantiationTest {
 
     @Test
     void supportsClassesWithoutNoArgConstructor() {
-        assertTrue(instantiation.supports(WithoutNoArgConstructor.class));
+        assertTrue(instantiation.supports(NonSerializableWithoutNoArgConstructor.class));
     }
 
     @Test
@@ -46,7 +46,7 @@ class UnsafeInstantiationTest {
 
     @Test
     void instantiatesClassesWithoutNoArgConstructor() throws Exception {
-        Object instance = instantiation.newInstance(WithoutNoArgConstructor.class);
+        Object instance = instantiation.newInstance(NonSerializableWithoutNoArgConstructor.class);
 
         assertThat(instance, is(notNullValue()));
     }