/* * Copyright (c) 2011, 2011, Oracle and/or its affiliates. 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. */ package com.sun.max.vm.hosted; import java.util.*; import com.sun.cri.bytecode.*; import com.sun.max.annotate.*; import com.sun.max.vm.classfile.*; import com.sun.max.vm.classfile.constant.*; import com.sun.max.vm.jdk.*; import com.sun.max.vm.type.*; /** * The class {@linkplain sun.misc.Unsafe} provides several methods to query the offset of fields within an object * and to layout of arrays. These offsets are different when running in a hosted environment during boot image generation * compared to the offsets used by Maxine (which depend on the object and array layout and are thus configurable by schemes). * Therefore, it is necessary to patch offsets when writing them into the boot image, which is done by the class {@linkplain JDKInterceptor}. * However, finding the classes and fields that need interception is tricky: there is no list of JDK classes that use Unsafe. * Missing interceptions lead to hard to find errors at run time. * * This class provides a heuristic to find usages of Unsafe that need interception. It is based on the observations that queries * for offsets are usually in the same class that also store the offsets. Therefore, every class that queries for an offset must * have at least one interceptor registered. There is a whitelist to filter false positives. * * The heuristic cannot detect wrong definitions of interceptors, and missing definition of interceptors when there is already at least * one interceptor defined for a class. However, it will raise an alarm when a previously uknown class is added to the boot image that * queries field offsets. */ @HOSTED_ONLY public class UnsafeUsageChecker { /** * Classes that have a call to one of the checked methods, but were inspected to not store any offsets in fields. */ private static final Set<String> whitelist = new HashSet<String>(Arrays.asList(new String[] { JDK.sun_misc_Unsafe.className(), JDK.java_util_concurrent_SynchronousQueue.className(), })); /** * The set of classes discovered during class loading that contain a call to one of the checked methods. */ private static Set<String> classesUsingUnsafe = new HashSet<String>(); /** * Type descriptor of the Unsafe class. */ private static final TypeDescriptor unsafeType = JavaTypeDescriptor.getDescriptorForTupleType(JDK.sun_misc_Unsafe.javaClass()); /** * List of checked methods in the Unsafe class that retrieve field or array offsets. */ private static final Set<Utf8Constant> checkedMethodNames = new HashSet<Utf8Constant>(Arrays.asList(new Utf8Constant[] { SymbolTable.makeSymbol("objectFieldOffset"), SymbolTable.makeSymbol("staticFieldOffset"), SymbolTable.makeSymbol("staticFieldBase"), SymbolTable.makeSymbol("fieldOffset"), SymbolTable.makeSymbol("arrayBaseOffset"), SymbolTable.makeSymbol("arrayIndexScale"), })); /** * Returns true if the given class calls any of the checked methods. */ public static boolean isClassUsingUnsafe(String className) { return classesUsingUnsafe.contains(className); } /** * Called by the class loader for every loaded method. Note that we also need to analyze static class initializers that * are not present in the boot image because they were already executed during boot image generation. This method must * be called before this filtering. Because of that, there is no ClassActor or MethodActor available yet. * * @param classDescriptor The class that contains the method. * @param method The name of the method that is analyzed. It is actually not used, but useful for debug printing. * @param code The code of the method. */ public static void methodLoadedHook(TypeDescriptor classDescriptor, Utf8Constant method, CodeAttribute code) { if (code == null) { return; } BytecodeStream stream = new BytecodeStream(code.code()); int bci = 0; while (bci < stream.endBCI()) { switch (stream.currentBC()) { case Bytecodes.INVOKESTATIC: case Bytecodes.INVOKESPECIAL: case Bytecodes.INVOKEVIRTUAL: case Bytecodes.INVOKEINTERFACE: MethodRefConstant methodRef = code.cp.methodAt(stream.readCPI()); if (methodRef.holder(code.cp) == unsafeType && checkedMethodNames.contains(methodRef.name(code.cp)) && !whitelist.contains(classDescriptor.toJavaString())) { classesUsingUnsafe.add(classDescriptor.toJavaString()); } break; } stream.next(); bci = stream.currentBCI(); } } }