From 802f48f50b378c1495570a33c0a02213dba93c6d Mon Sep 17 00:00:00 2001 From: Maxim Kartashev Date: Mon, 26 Sep 2022 16:25:24 +0300 Subject: [PATCH] JBR-4839 Report if wrong shared library is detected at run time (cherry picked from commit e8c1913562c080a76ad13dafc1b810cd1454ae94) --- .../macosx/native/libawt_lwawt/awt/InitIDs.m | 25 +++++ .../sun/font/FontManagerNativeLibrary.java | 74 +++++++++++++ .../unix/native/libawt/awt/initIDs.c | 73 ++++++++++++ test/jdk/jb/java/awt/Font/LibrariesCheck.java | 104 ++++++++++++++++++ 4 files changed, 276 insertions(+) create mode 100644 test/jdk/jb/java/awt/Font/LibrariesCheck.java diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/InitIDs.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/InitIDs.m index 289e63ba98c8..6a0d07c7de16 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/InitIDs.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/InitIDs.m @@ -24,6 +24,8 @@ */ #import +#import +#import #import "InitIDs.h" @@ -161,3 +163,26 @@ JNIEXPORT void JNICALL Java_java_awt_event_MouseEvent_initIDs (JNIEnv *env, jclass cls) { } + +JNIEXPORT jarray JNICALL Java_sun_font_FontManagerNativeLibrary_loadedLibraries + (JNIEnv *env, jclass cls) +{ + const uint32_t count = _dyld_image_count(); + + jclass stringClazz = (*env)->FindClass(env, "java/lang/String"); + CHECK_NULL_RETURN(stringClazz, NULL); + jarray libsArray = (*env)->NewObjectArray(env, count, stringClazz, NULL); + JNU_CHECK_EXCEPTION_RETURN(env, NULL); + + for (uint32_t i = 0; i < count; i++) { + const char * name = _dyld_get_image_name(i); + if (name) { + jstring jName = (*env)->NewStringUTF(env, name); + JNU_CHECK_EXCEPTION_RETURN(env, NULL); + (*env)->SetObjectArrayElement(env, libsArray, i, jName); + JNU_CHECK_EXCEPTION_RETURN(env, NULL); + } + } + + return libsArray; +} diff --git a/src/java.desktop/share/classes/sun/font/FontManagerNativeLibrary.java b/src/java.desktop/share/classes/sun/font/FontManagerNativeLibrary.java index df67a40fd141..c01f9aefe6fa 100644 --- a/src/java.desktop/share/classes/sun/font/FontManagerNativeLibrary.java +++ b/src/java.desktop/share/classes/sun/font/FontManagerNativeLibrary.java @@ -26,6 +26,10 @@ package sun.font; import sun.awt.OSInfo; +import sun.util.logging.PlatformLogger; + +import java.nio.file.Path; +import java.util.Set; @SuppressWarnings("restricted") public class FontManagerNativeLibrary { @@ -53,6 +57,8 @@ public class FontManagerNativeLibrary { System.loadLibrary("freetype"); } System.loadLibrary("fontmanager"); + + checkLibraries(); } /* @@ -63,4 +69,72 @@ public class FontManagerNativeLibrary { * Actual loading is performed by static initializer. */ public static void load() {} + + + private static class Holder { + private static final PlatformLogger errorLog = PlatformLogger.getLogger("sun.font.FontManagerNativeLibrary"); + } + public static void checkLibraries() { + final String osName = System.getProperty("os.name"); + final boolean isMacOS = osName.startsWith("Mac"); + final boolean isLinux = osName.startsWith("Linux"); + + if (!(isMacOS || isLinux)) return; + + final String[] loadedLibraries = loadedLibraries(); + + final String libawtName = isMacOS ? "libawt.dylib" : "libawt.so"; + final String bootPath = System.getProperty("sun.boot.library.path"); + final Path basePath = bootPath != null + ? Path.of(bootPath) + : getBasePath(libawtName, loadedLibraries); + + if (basePath == null) { + // Nothing to check if the native code failed to report where libawt is + return; + } + + final String libraryPathEnvVarName = isMacOS + ? "DYLD_LIBRARY_PATH" + : "LD_LIBRARY_PATH"; + final Set ourLibraries = isMacOS + ? Set.of("libawt_lwawt.dylib", "libfontmanager.dylib", "libfreetype.dylib") + : Set.of("libawt_xawt.so", "libfontmanager.so"); + + boolean warnedAlready = false; + for (String pathName: loadedLibraries) { + if (pathName == null) + continue; + final Path libPath = Path.of(pathName); + final Path libFileName = libPath.getFileName(); + if (ourLibraries.contains(libFileName.toString())) { + final Path libDirectory = libPath.getParent(); + if (!basePath.equals(libDirectory)) { + Holder.errorLog.severe("Library '" + libFileName + + "' loaded from '" + libDirectory + "' instead of '" + + basePath + "'"); + if (!warnedAlready) { + warnedAlready = true; + final String libraryPathOverride = System.getenv(libraryPathEnvVarName); + if (libraryPathOverride != null) { + Holder.errorLog.severe("Note: " + libraryPathEnvVarName + + " is set to '" + libraryPathOverride + "'"); + } + } + } + } + } + } + + private static Path getBasePath(String libawtName, String[] libs) { + Path basePath = null; + for (String path: libs) { + if (path.endsWith(libawtName)) { + basePath = Path.of(path).getParent().normalize(); + } + } + return basePath; + } + + private static native String[] loadedLibraries(); } diff --git a/src/java.desktop/unix/native/libawt/awt/initIDs.c b/src/java.desktop/unix/native/libawt/awt/initIDs.c index ddba5d1338a9..a462b93ef96b 100644 --- a/src/java.desktop/unix/native/libawt/awt/initIDs.c +++ b/src/java.desktop/unix/native/libawt/awt/initIDs.c @@ -33,6 +33,10 @@ #include "jni_util.h" +#include +#include +#include + /* * This file contains stubs for JNI field and method id initializers * which are used in the win32 awt. @@ -91,3 +95,72 @@ Java_java_awt_event_MouseEvent_initIDs (JNIEnv *env, jclass clazz) { } + +struct shared_libs { + int count; + int index; + char ** names; +}; + +static int +dl_iterate_callback + (struct dl_phdr_info *info, size_t size, void *data) +{ + struct shared_libs *libs = (struct shared_libs*)data; + if (libs->names == NULL) { + libs->count++; + } else { + // The number of libraries may have grown since the last time we asked. + if (libs->index < libs->count) { + libs->names[libs->index++] = strdup(info->dlpi_name); + } + } + + return 0; +} + +static jarray +convert_to_java_array + (JNIEnv *env, struct shared_libs* libs) +{ + jclass stringClazz = (*env)->FindClass(env, "java/lang/String"); + CHECK_NULL_RETURN(stringClazz, NULL); + jarray libsArray = (*env)->NewObjectArray(env, libs->count, stringClazz, NULL); + JNU_CHECK_EXCEPTION_RETURN(env, NULL); + + for (uint32_t i = 0; i < libs->count; i++) { + const char * name = libs->names[i]; + if (name) { + jstring jName = (*env)->NewStringUTF(env, name); + JNU_CHECK_EXCEPTION_RETURN(env, NULL); + (*env)->SetObjectArrayElement(env, libsArray, i, jName); + JNU_CHECK_EXCEPTION_RETURN(env, NULL); + } + } + + return libsArray; +} + +JNIEXPORT jarray JNICALL +Java_sun_font_FontManagerNativeLibrary_loadedLibraries + (JNIEnv *env, jclass cls) +{ + struct shared_libs libs = {0, 0, NULL}; + dl_iterate_phdr(&dl_iterate_callback, &libs); + if (libs.count <= 0) { + return NULL; + } + + libs.names = (char **) calloc(libs.count, sizeof(libs.names[0])); + CHECK_NULL_RETURN(libs.names, NULL); + dl_iterate_phdr(&dl_iterate_callback, &libs); + + jarray libsArray = convert_to_java_array(env, &libs); + + for (uint32_t i = 0; i < libs.count; i++) { + free(libs.names[i]); + } + free(libs.names); + + return libsArray; +} diff --git a/test/jdk/jb/java/awt/Font/LibrariesCheck.java b/test/jdk/jb/java/awt/Font/LibrariesCheck.java new file mode 100644 index 000000000000..4406b47fed3d --- /dev/null +++ b/test/jdk/jb/java/awt/Font/LibrariesCheck.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022, JetBrains s.r.o.. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary Verifies that a warning is issued when certain bundled libraries + * are loaded NOT from 'test.jdk'. + * @requires os.family == "mac" + * @library /test/lib + * @run main LibrariesCheck + */ + +import java.awt.Robot; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.JLabel; + +import java.nio.file.Path; +import java.nio.file.Files; +import java.util.Map; + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.Platform; + +public class LibrariesCheck { + public static void main(String[] args) throws Exception { + if (args.length > 0 && args[0].equals("runtest")) { + LibrariesCheck test = new LibrariesCheck(); + } else { + // Run the test with DYLD_LIBRARY_PATH pointing to a directory + // with a substitute libfontmanager. JBR is supposed to detect this + // and complain with a log message. + String dynamicLibPathEnvVarName = "DYLD_LIBRARY_PATH"; + Path tempDir = Files.createTempDirectory(LibrariesCheck.class.getName()); + String testLibName = System.mapLibraryName("fontmanager"); + Path testLibPath = tempDir.resolve(testLibName); + try { + Path jdkLibPath = Platform.libDir(); + Files.copy(jdkLibPath.resolve(testLibName), testLibPath); + + ProcessBuilder pb = ProcessTools.createTestJvm( + LibrariesCheck.class.getName(), + "runtest"); + + Map env = pb.environment(); + env.put(dynamicLibPathEnvVarName, tempDir.toString()); + + OutputAnalyzer oa = ProcessTools.executeProcess(pb); + oa.shouldContain("SEVERE").shouldContain(testLibName) + .shouldContain(dynamicLibPathEnvVarName); + } finally { + Files.delete(testLibPath); + Files.delete(tempDir); + } + } + } + + JFrame frame; + + LibrariesCheck() throws Exception { + try { + SwingUtilities.invokeAndWait(() -> { + frame = new JFrame("LibrariesCheck"); + JPanel panel = new JPanel(); + panel.add(new JLabel("some text")); + frame.getContentPane().add(panel); + frame.pack(); + frame.setVisible(true); + }); + + Robot robot = new Robot(); + robot.waitForIdle(); + + SwingUtilities.invokeAndWait(() -> { + frame.dispose(); + }); + } finally { + if (frame != null) { + frame.dispose(); + } + } + } +}