| /******************************************************************************* |
| * Copyright (c) 2000, 2021 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.ui.leaktest.reftracker; |
| |
| import java.lang.ref.Reference; |
| import java.lang.ref.SoftReference; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| |
| import org.eclipse.jdt.testplugin.JavaTestPlugin; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.core.runtime.Status; |
| |
| /** |
| * Tracks all instances reachable though reflection from a given root object. To visit all elements in a VM |
| * pass a class loader as class loaders know all loaded classes and classes know all static fields. This normally covers |
| * all references except instances that are only referenced by local variables or by native roots |
| */ |
| public final class ReferenceTracker { |
| |
| private static final String CURRENT_PKG_NAME= ReferenceTracker.class.getPackage().getName(); |
| private static final String REFERENCE_NAME= Reference.class.getName(); |
| |
| private IdentityHashSet fVisitedElements; |
| private final ReferenceVisitor fReferenceVisitor; |
| private FIFOQueue fQueue; |
| private MultiStatus fStatus; |
| |
| private boolean fSkipWeakOrSoft; |
| |
| public ReferenceTracker(ReferenceVisitor visitor) { |
| fReferenceVisitor= visitor; |
| fStatus= null; |
| fVisitedElements= null; |
| fQueue= null; |
| fSkipWeakOrSoft= true; |
| } |
| |
| private static boolean isInteresting(Class<?> clazz) { |
| String name= clazz.getName(); |
| if (name.startsWith(CURRENT_PKG_NAME) || name.startsWith("sun.reflect.")) { //$NON-NLS-1$ |
| return false; |
| } |
| return true; |
| } |
| |
| private void followArrayReference(ReferencedObject prev, int index, Object to) { |
| fQueue.add(new ReferencedArrayElement(prev, index, to)); |
| } |
| |
| private void followFieldReference(ReferencedObject ref, Object curr, Field fld) { |
| try { |
| if (fSkipWeakOrSoft && isWeakOrSoftReference(curr, fld)) { |
| return; |
| } |
| boolean isAccessible= setAccessible(fld, true); |
| try { |
| Object fieldVal= fld.get(curr); |
| if (fieldVal != null) { |
| fQueue.add(new ReferencedFieldElement(ref, fld, fieldVal)); |
| } |
| } finally { |
| setAccessible(fld, isAccessible); |
| } |
| } catch (IllegalArgumentException | IllegalAccessException | ExceptionInInitializerError e) { |
| handleError(e, fld); |
| } |
| } |
| |
| private static boolean isWeakOrSoftReference(Object curr, Field fld) { |
| if (REFERENCE_NAME.equals(fld.getDeclaringClass().getName())) { |
| return "referent".equals(fld.getName()) |
| && (curr instanceof WeakReference || curr instanceof SoftReference); |
| } |
| return false; |
| } |
| |
| |
| private void handleError(Throwable t, Field fld) { |
| fStatus.add(new Status(IStatus.ERROR, JavaTestPlugin.getPluginId(), IStatus.ERROR, "Problem on access of " + fld.toString(), t)); |
| } |
| |
| private void followStaticReferences(Class<?> classInstance) { |
| for (Field fld : classInstance.getDeclaredFields()) { |
| if (isStatic(fld.getModifiers()) && !fld.getType().isPrimitive()) { |
| followFieldReference(new RootReference(classInstance), null, fld); |
| } |
| } |
| } |
| |
| private static boolean setAccessible(Field fld, boolean enable) { |
| try { |
| boolean isAccessible= fld.isAccessible(); |
| if (isAccessible != enable) { |
| fld.setAccessible(enable); |
| } |
| return isAccessible; |
| } catch (RuntimeException ex) { |
| throw new RuntimeException("JVM settings `--add-modules ALL-SYSTEM --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens jdk.localedata/sun.util.resources.cldr.provider=ALL-UNNAMED --add-opens jdk.localedata/sun.util.resources.provider=ALL-UNNAMED --add-opens java.base/jdk.internal.module=ALL-UNNAMED --add-opens java.base/java.lang.module=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.ref=ALL-UNNAMED --add-opens java.base/jdk.internal.math=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/jdk.internal.icu.impl=ALL-UNNAMED --add-opens java.base/jdk.internal.icu.text=ALL-UNNAMED --illegal-access=permit` are probably missing.\nException : \n" + ex.getMessage(), ex); |
| } |
| } |
| |
| private void visit(ReferencedObject ref) { |
| Object curr= ref.getValue(); |
| Class<?> currClass= curr.getClass(); |
| if (!isInteresting(currClass)) { |
| return; |
| } |
| |
| boolean firstVisit= fVisitedElements.add(curr); |
| boolean continueVisiting= fReferenceVisitor.visit(ref, curr.getClass(), firstVisit); |
| |
| if (!firstVisit || !continueVisiting) { |
| return; |
| } |
| |
| if (currClass.isArray()) { |
| if (currClass.getComponentType().isPrimitive()) { |
| return; |
| } |
| Object[] array= (Object[]) curr; |
| for (int i= 0; i < array.length; i++) { |
| Object elem= array[i]; |
| if (elem != null) { |
| followArrayReference(ref, i, elem); |
| } |
| } |
| } else if (currClass.isPrimitive()) { |
| return; |
| } else { |
| if (currClass == Class.class) { |
| followStaticReferences((Class<?>) curr); |
| } |
| do { |
| for (Field fld : currClass.getDeclaredFields()) { |
| if (!isStatic(fld.getModifiers()) && !fld.getType().isPrimitive()) { |
| followFieldReference(ref, curr, fld); |
| } |
| } |
| currClass= currClass.getSuperclass(); |
| } while (currClass != null); |
| } |
| } |
| |
| private static boolean isStatic(int modifiers) { |
| return (modifiers & Modifier.STATIC) != 0; |
| } |
| |
| public IStatus start(Object root) { |
| fVisitedElements= new IdentityHashSet(1 << 21); // 2 M -> 8 MB |
| fQueue= new FIFOQueue(100); |
| fStatus= new MultiStatus(JavaTestPlugin.getPluginId(), IStatus.OK, "Problem tracking resources", null); |
| |
| try { |
| visit(new RootReference(root)); |
| |
| FIFOQueue queue= fQueue; |
| |
| ReferencedObject next= (ReferencedObject) queue.poll(); |
| while (next != null) { |
| visit(next); |
| next= (ReferencedObject) queue.poll(); |
| } |
| } finally { |
| // make sure not to hold on any references |
| fVisitedElements.clear(); |
| fVisitedElements= null; |
| fQueue= null; |
| } |
| return fStatus; |
| } |
| |
| } |