001/**
002 * Copyright (C) 2006-2025 Talend Inc. - www.talend.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.talend.sdk.component.runtime.manager.asm;
017
018import static java.util.Objects.requireNonNull;
019import static lombok.AccessLevel.PRIVATE;
020
021import java.lang.reflect.AccessibleObject;
022import java.lang.reflect.Field;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.security.AccessController;
026import java.security.PrivilegedAction;
027import java.security.ProtectionDomain;
028import java.util.stream.Stream;
029
030import org.talend.sdk.component.classloader.ConfigurableClassLoader;
031import org.talend.sdk.component.runtime.reflect.JavaVersion;
032
033import lombok.NoArgsConstructor;
034
035@NoArgsConstructor(access = PRIVATE)
036public final class Unsafes {
037
038    private static final Object UNSAFE;
039
040    private static final Object INTERNAL_UNSAFE;
041
042    private static final Method UNSAFE_DEFINE_CLASS;
043
044    static {
045        Class<?> unsafeClass;
046        final int javaVersion = JavaVersion.major();
047        if (javaVersion > 8 && javaVersion < 17) {
048            try {
049                /**
050                 * Disable Access Warnings:
051                 *
052                 * <pre>
053                 * {@code
054                 * WARNING: An illegal reflective access operation has occurred
055                 * WARNING: Illegal reflective access by org.talend.sdk.component.runtime.manager.asm.Unsafes \
056                 * (file:/xxxx/component-runtime-manager-x.xx.x.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int)
057                 * WARNING: Please consider reporting this to the maintainers of org.talend.sdk.component.runtime.manager.asm.Unsafes
058                 * WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
059                 * WARNING: All illegal access operations will be denied in a future release
060                }
061                 * </pre>
062                 */
063                Class unsafeClazz = Class.forName("sun.misc.Unsafe");
064                Field field = unsafeClazz.getDeclaredField("theUnsafe");
065                field.setAccessible(true);
066                Object unsafe = field.get(null);
067                Method putObjectVolatile =
068                        unsafeClazz.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class);
069                Method staticFieldOffset = unsafeClazz.getDeclaredMethod("staticFieldOffset", Field.class);
070                Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
071                Field loggerField = loggerClass.getDeclaredField("logger");
072                Long offset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
073                putObjectVolatile.invoke(unsafe, loggerClass, offset, null);
074            } catch (Exception e) {
075                System.err.println("Disabling unsafe warnings failed: " + e.getMessage());
076            }
077        }
078        try {
079            unsafeClass = AccessController
080                    .doPrivileged((PrivilegedAction<Class<?>>) () -> Stream
081                            .of(Thread.currentThread().getContextClassLoader(), ClassLoader.getSystemClassLoader())
082                            .flatMap(classloader -> Stream
083                                    .of("sun.misc.Unsafe", "jdk.internal.misc.Unsafe")
084                                    .flatMap(name -> {
085                                        try {
086                                            return Stream.of(classloader.loadClass(name));
087                                        } catch (final ClassNotFoundException e) {
088                                            return Stream.empty();
089                                        }
090                                    }))
091                            .findFirst()
092                            .orElseThrow(() -> new IllegalStateException("Cannot get Unsafe")));
093        } catch (final Exception e) {
094            throw new IllegalStateException("Cannot get Unsafe class", e);
095        }
096
097        UNSAFE = AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
098            try {
099                final Field field = unsafeClass.getDeclaredField("theUnsafe");
100                field.setAccessible(true);
101                return field.get(null);
102            } catch (final Exception e) {
103                throw new IllegalStateException(e);
104            }
105        });
106        INTERNAL_UNSAFE = AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
107            try { // j11, unwrap unsafe, it owns defineClass now and no more theUnsafe
108                final Field theInternalUnsafe = unsafeClass.getDeclaredField("theInternalUnsafe");
109                theInternalUnsafe.setAccessible(true);
110                return theInternalUnsafe.get(null).getClass();
111            } catch (final Exception notJ11OrMore) {
112                return UNSAFE;
113            }
114        });
115
116        if (UNSAFE != null) {
117            UNSAFE_DEFINE_CLASS = AccessController.doPrivileged((PrivilegedAction<Method>) () -> {
118                try {
119                    return INTERNAL_UNSAFE
120                            .getClass()
121                            .getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class,
122                                    ClassLoader.class, ProtectionDomain.class);
123                } catch (final Exception e) {
124                    return null;
125                }
126            });
127
128            try {
129                final Class<?> rootLoaderClass = Class.forName("java.lang.ClassLoader");
130                rootLoaderClass
131                        .getDeclaredMethod("defineClass",
132                                new Class[] { String.class, byte[].class, int.class, int.class })
133                        .setAccessible(true);
134                rootLoaderClass
135                        .getDeclaredMethod("defineClass",
136                                new Class[] { String.class, byte[].class, int.class, int.class,
137                                        ProtectionDomain.class })
138                        .setAccessible(true);
139            } catch (final Exception e) {
140                try { // some j>8, since we have unsafe let's use it
141                    final Class<?> rootLoaderClass = Class.forName("java.lang.ClassLoader");
142                    final Method objectFieldOffset =
143                            UNSAFE.getClass().getDeclaredMethod("objectFieldOffset", Field.class);
144                    final Method putBoolean =
145                            UNSAFE.getClass().getDeclaredMethod("putBoolean", Object.class, long.class, boolean.class);
146                    objectFieldOffset.setAccessible(true);
147                    final long accOffset = Long.class
148                            .cast(objectFieldOffset
149                                    .invoke(UNSAFE, AccessibleObject.class.getDeclaredField("override")));
150                    putBoolean
151                            .invoke(UNSAFE,
152                                    rootLoaderClass
153                                            .getDeclaredMethod("defineClass",
154                                                    new Class[] { String.class, byte[].class, int.class, int.class }),
155                                    accOffset, true);
156                    putBoolean
157                            .invoke(UNSAFE,
158                                    rootLoaderClass
159                                            .getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class,
160                                                    int.class, int.class, ProtectionDomain.class }),
161                                    accOffset, true);
162                } catch (final Exception ex) {
163                    // no-op: no more supported by the JVM
164                }
165            }
166        } else {
167            UNSAFE_DEFINE_CLASS = null;
168        }
169    }
170
171    /**
172     * The 'defineClass' method on the ClassLoader is private, thus we need to
173     * invoke it via reflection.
174     *
175     * @param classLoader the classloader to use to define the proxy.
176     * @param proxyName the class name to define.
177     * @param proxyBytes the bytes of the class to define.
178     * @param <T> the Class type
179     *
180     * @return the Class which got loaded in the classloader
181     */
182    public static <T> Class<T> defineAndLoadClass(final ClassLoader classLoader, final String proxyName,
183            final byte[] proxyBytes) {
184        if (ConfigurableClassLoader.class.isInstance(classLoader)) {
185            return (Class<T>) ConfigurableClassLoader.class
186                    .cast(classLoader)
187                    .registerBytecode(proxyName.replace('/', '.'), proxyBytes);
188        }
189        Class<?> clazz = classLoader.getClass();
190
191        Method defineClassMethod = null;
192        do {
193            try {
194                defineClassMethod =
195                        clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
196            } catch (NoSuchMethodException e) {
197                // do nothing, we need to search the superclass
198            }
199
200            clazz = clazz.getSuperclass();
201        } while (defineClassMethod == null && clazz != Object.class);
202
203        if (defineClassMethod != null && !defineClassMethod.isAccessible()) {
204            try {
205                defineClassMethod.setAccessible(true);
206            } catch (final RuntimeException re) { // likely j9, let's use unsafe
207                defineClassMethod = null;
208            }
209        }
210
211        try {
212            Class<T> definedClass;
213
214            if (defineClassMethod != null) {
215                definedClass =
216                        (Class<T>) defineClassMethod.invoke(classLoader, proxyName, proxyBytes, 0, proxyBytes.length);
217            } else {
218                requireNonNull(UNSAFE_DEFINE_CLASS, "No Unsafe.defineClass available");
219                definedClass = (Class<T>) UNSAFE_DEFINE_CLASS
220                        .invoke(UNSAFE, proxyName, proxyBytes, 0, proxyBytes.length, classLoader, null);
221            }
222
223            return (Class<T>) Class.forName(definedClass.getName(), true, classLoader);
224        } catch (final InvocationTargetException le) {
225            if (LinkageError.class.isInstance(le.getCause())) {
226                try {
227                    return (Class<T>) Class.forName(proxyName.replace('/', '.'), true, classLoader);
228                } catch (ClassNotFoundException e) {
229                    // default error handling
230                }
231            }
232            throw new IllegalStateException(le.getCause());
233        } catch (final Throwable e) {
234            throw new IllegalStateException(e);
235        }
236    }
237}