/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed 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 com.intellij.openapi.util.objectTree; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.SystemInfo; import com.intellij.util.concurrency.AtomicFieldUpdater; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.FilteringIterator; import com.intellij.util.containers.WeakInterner; import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; import java.util.Arrays; /** * Please don't look, there's nothing interesting here. * <p/> * <p/> * <p/> * <p/> * If you insist, JVM stores stacktrace information in compact form in Throwable.backtrace field, but blocks reflective access to this field. * This class uses this field for comparing Throwables. * The available method Throwable.getStackTrace() unfortunately can't be used for that because it's * 1) too slow and 2) explodes Throwable retained size by polluting Throwable.stackTrace fields. */ public class ThrowableInterner { private static final WeakInterner<Throwable> myTraceInterner = new WeakInterner<Throwable>(new TObjectHashingStrategy<Throwable>() { @Override public int computeHashCode(Throwable throwable) { String message = throwable.getMessage(); if (message != null) { return message.hashCode(); } Object[] backtrace = getBacktrace(throwable); if (backtrace != null) { Object[] stack = (Object[])ContainerUtil.find(backtrace, FilteringIterator.instanceOf(Object[].class)); return Arrays.hashCode(stack); } return Arrays.hashCode(throwable.getStackTrace()); } @Override public boolean equals(Throwable o1, Throwable o2) { if (o1 == o2) return true; if (o1 == null || o2 == null) return false; if (!Comparing.equal(o1.getClass(), o2.getClass())) return false; if (!Comparing.equal(o1.getMessage(), o2.getMessage())) return false; if (!equals(o1.getCause(), o2.getCause())) return false; Object[] backtrace1 = getBacktrace(o1); Object[] backtrace2 = getBacktrace(o2); if (backtrace1 != null && backtrace2 != null) { return Arrays.deepEquals(backtrace1, backtrace2); } return Arrays.equals(o1.getStackTrace(), o2.getStackTrace()); } }); private static final long BACKTRACE_FIELD_OFFSET; static { if (SystemInfo.isJavaVersionAtLeast("1.9")) { try { Field backtrace = Throwable.class.getDeclaredField("backtrace"); BACKTRACE_FIELD_OFFSET = backtrace == null ? -1 : AtomicFieldUpdater.getUnsafe().objectFieldOffset(backtrace); if (BACKTRACE_FIELD_OFFSET == -1 || !(AtomicFieldUpdater.getUnsafe().getObject(new Throwable(), BACKTRACE_FIELD_OFFSET) instanceof Object[])) { throw new RuntimeException( "Unknown layout: " + Arrays.asList(Throwable.class.getDeclaredFields()) + ". Please specify -Didea.disposer.debug=off in consulo.properties" + " to suppress"); } } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } else if ((SystemInfo.isOracleJvm || SystemInfo.isJetbrainsJvm) && SystemInfo.isJavaVersionAtLeast("1.7")) { Field firstField = Throwable.class.getDeclaredFields()[1]; long firstFieldOffset = AtomicFieldUpdater.getUnsafe().objectFieldOffset(firstField); BACKTRACE_FIELD_OFFSET = firstFieldOffset == 12 ? 8 : firstFieldOffset == 16 ? 12 : firstFieldOffset == 24 ? 16 : -1; if (BACKTRACE_FIELD_OFFSET == -1 || !firstField.getName().equals("detailMessage") || !(AtomicFieldUpdater.getUnsafe().getObject(new Throwable(), BACKTRACE_FIELD_OFFSET) instanceof Object[])) { throw new RuntimeException( "Unknown layout: " + firstField + ";" + firstFieldOffset + ". Please specify -Didea.disposer.debug=off in consulo.properties to suppress"); } } else { BACKTRACE_FIELD_OFFSET = -1; } } private static Object[] getBacktrace(@NotNull Throwable throwable) { // the JVM blocks access to Throwable.backtrace via reflection Object backtrace = BACKTRACE_FIELD_OFFSET == -1 ? null : AtomicFieldUpdater.getUnsafe().getObject(throwable, BACKTRACE_FIELD_OFFSET); // obsolete jdk return backtrace instanceof Object[] && ((Object[])backtrace).length == 5 ? (Object[])backtrace : null; } @NotNull public static Throwable intern(@NotNull Throwable throwable) { return getBacktrace(throwable) == null ? throwable : myTraceInterner.intern(throwable); } }