/* * Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and * contributors. All rights reserved. * * 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.noctarius.tengi.core.serialization.debugger.impl; import com.noctarius.tengi.core.serialization.debugger.DebuggableProtocol; import com.noctarius.tengi.core.serialization.debugger.SerializationDebugger; import com.noctarius.tengi.spi.buffer.ReadableMemoryBuffer; import com.noctarius.tengi.spi.serialization.Protocol; import com.noctarius.tengi.spi.serialization.codec.Codec; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class DefaultSerializationDebugger implements SerializationDebugger { /** * Static holder class to prevent eager initialization but provides thread-safe instantiation * of the default serialization debugger implementation. */ public static final class Holder { /** * Static instance of the default serialization debugger implementation */ public static final SerializationDebugger INSTANCE = new DefaultSerializationDebugger(); } private static final ThreadLocal<Stack> STACK = new ThreadLocal<Stack>() { @Override protected Stack initialValue() { return new Stack(); } }; private DefaultSerializationDebugger() { } @Override public void push(Protocol protocol, Codec codec, Process process, Object value) { Stack stack = STACK.get(); StackTraceElement stackTraceElement = buildStackFrame(protocol, codec, process, value); if (stackTraceElement == null) { return; } Node newHead = new Node(stackTraceElement); Node oldHead; do { oldHead = stack.head; newHead.next = oldHead; } while (!Stack.UPDATER.compareAndSet(stack, oldHead, newHead)); } @Override public void pop() { Stack stack = STACK.get(); Node oldHead; Node newHead; do { oldHead = stack.head; if (oldHead == null) { return; } newHead = oldHead.next; } while (!Stack.UPDATER.compareAndSet(stack, oldHead, newHead)); } @Override public void fixFramesToStackTrace(Throwable throwable) { int serializationStackPosition = 0; StackTraceElement[] serializationStack = buildSerializationStack(); StackTraceElement[] stackTrace = throwable.getStackTrace(); for (int i = 0; i < stackTrace.length; i++) { if (matches(stackTrace[i]) // && equals(stackTrace[i], serializationStack[serializationStackPosition])) { stackTrace[i] = serializationStack[serializationStackPosition++]; if (serializationStackPosition >= serializationStack.length) { break; } } } throwable.setStackTrace(stackTrace); } private boolean equals(StackTraceElement current, StackTraceElement stored) { if (!current.getClassName().equals(stored.getClassName())) { return false; } if (!current.getFileName().equals(stored.getFileName())) { return false; } String[] split = stored.getMethodName().split(" "); if (split.length == 1) { return false; } return current.getMethodName().equals(split[0]); } private StackTraceElement[] buildSerializationStack() { try { Stack stack = STACK.get(); List<Node> nodes = new ArrayList<>(20); // Read all nodes from stack Node node = stack.head; do { nodes.add(node); } while ((node = node.next) != null); StackTraceElement[] stackTraceElements = new StackTraceElement[nodes.size()]; for (int i = 0; i < nodes.size(); i++) { stackTraceElements[i] = nodes.get(i).stackTraceElement; } return stackTraceElements; } catch (Exception e) { return new StackTraceElement[0]; } finally { STACK.remove(); } } private StackTraceElement buildStackFrame(Protocol protocol, Codec codec, Process process, Object value) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); int position = -1; for (int i = 0; i < stackTrace.length; i++) { if ("push".equals(stackTrace[i].getMethodName()) && matches(stackTrace[i + 1])) { position = i + 1; break; } } if (position == -1) { // No changeable stack information available return null; } StackTraceElement old = stackTrace[position]; String methodName = buildMethodName(protocol, codec, process, old.getMethodName(), value); return new StackTraceElement(old.getClassName(), methodName, old.getFileName(), old.getLineNumber()); } private boolean matches(StackTraceElement stackTraceElement) { if (!"writeObject".equals(stackTraceElement.getMethodName()) // && !"readObject".equals(stackTraceElement.getMethodName()) // && !stackTraceElement.getMethodName().contains("$writeObject$") // && !stackTraceElement.getMethodName().contains("$readObject$")) { return false; } try { Class<?> clazz = Class.forName(stackTraceElement.getClassName()); return Codec.class.isAssignableFrom(clazz); } catch (Exception e) { return false; } } private String buildMethodName(Protocol protocol, Codec codec, // Process process, String methodName, Object value) { Class<?> type = findType(protocol, codec, process, value); String className = type == null ? "Unknown" : type.getName(); StringBuilder sb = new StringBuilder(methodName); sb.append(" [").append(process.name()).append(" => ").append("type=").append(className); if (Debugger.STORE_VALUES) { sb.append(", value=").append(value); } return sb.append("]").toString(); } private Class<?> findType(Protocol protocol, Codec codec, Process process, Object value) { if (value != null) { return value.getClass(); } if (process != Process.DESERIALIZE) { return null; } if (protocol instanceof DebuggableProtocol) { DebuggableProtocol dp = (DebuggableProtocol) protocol; return dp.findType(codec); } else { ReadableMemoryBuffer memoryBuffer = codec.getReadableMemoryBuffer(); int readerIndex = memoryBuffer.readerIndex(); try { return protocol.readTypeId(codec); } finally { memoryBuffer.readerIndex(readerIndex); } } } private static class Stack { private static final AtomicReferenceFieldUpdater<Stack, Node> UPDATER = // AtomicReferenceFieldUpdater.newUpdater(Stack.class, Node.class, "head"); private volatile Node head = null; } private static class Node { private final StackTraceElement stackTraceElement; private Node next; public Node(StackTraceElement stackTraceElement) { this.stackTraceElement = stackTraceElement; } } }