1e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov/******************************************************************************* 2398ee59bebad6835dab57b60157eff16d511709eMarc R. Hoffmann * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors 3e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * All rights reserved. This program and the accompanying materials 4e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * are made available under the terms of the Eclipse Public License v1.0 5e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * which accompanies this distribution, and is available at 6e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * http://www.eclipse.org/legal/epl-v10.html 7e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * 8e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * Contributors: 9e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * Marc R. Hoffmann - initial API and implementation 10e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * 11e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov *******************************************************************************/ 12e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovpackage org.jacoco.core.runtime; 13e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 14e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport java.util.logging.Handler; 15e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport java.util.logging.Level; 16e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport java.util.logging.LogRecord; 17e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport java.util.logging.Logger; 18e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 19e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport org.jacoco.core.internal.instr.InstrSupport; 20e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport org.objectweb.asm.MethodVisitor; 21e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovimport org.objectweb.asm.Opcodes; 22e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 23e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov/** 24e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * This {@link IRuntime} implementation uses the Java logging API to report 25e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * coverage data. 26e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * <p> 27e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * 28e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * The implementation uses a dedicated log channel. Instrumented classes call 29e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * {@link Logger#log(Level, String, Object[])} with the class identifier in the 30e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * first slot of the parameter array. The runtime implements a {@link Handler} 31e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * for this channel that puts the probe data structure into the first slot of 32e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * the parameter array. 33e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov */ 34e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikovpublic class LoggerRuntime extends AbstractRuntime { 35e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 36e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov private static final String CHANNEL = "jacoco-runtime"; 37e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 38e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov private final String key; 39e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 40e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov private final Logger logger; 41e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 42e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov private final Handler handler; 43e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 44e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov /** 45e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov * Creates a new runtime. 46e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov */ 47e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov public LoggerRuntime() { 48e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov super(); 49e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov this.key = Integer.toHexString(hashCode()); 50e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov this.logger = configureLogger(); 51e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov this.handler = new RuntimeHandler(); 52e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 53e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 54e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov private Logger configureLogger() { 55e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov final Logger l = Logger.getLogger(CHANNEL); 56e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov l.setUseParentHandlers(false); 57e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov l.setLevel(Level.ALL); 58e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov return l; 59e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 60e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 61e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov public int generateDataAccessor(final long classid, final String classname, 62e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov final int probecount, final MethodVisitor mv) { 63e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 64e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // The data accessor performs the following steps: 65e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // 66e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // final Object[] args = new Object[3]; 67e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // args[0] = Long.valueOf(classid); 68e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // args[1] = classname; 69e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // args[2] = Integer.valueOf(probecount); 70e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Logger.getLogger(CHANNEL).log(Level.INFO, key, args); 71e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // final byte[] probedata = (byte[]) args[0]; 72e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // 73e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Note that local variable 'args' is used at two places. As were not 74e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // allowed to allocate local variables we have to keep this value with 75e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // DUP and SWAP operations on the operand stack. 76e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 77e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // 1. Create parameter array: 78e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 7927e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann RuntimeData.generateArgumentArray(classid, classname, probecount, mv); 80e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 81e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Ljava/lang/Object; 82e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 83e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitInsn(Opcodes.DUP); 84e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 85e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[1]: [Ljava/lang/Object; 86e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Ljava/lang/Object; 87e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 88e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // 2. Call Logger: 89e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 90e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitLdcInsn(CHANNEL); 91e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/logging/Logger", 92351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann "getLogger", "(Ljava/lang/String;)Ljava/util/logging/Logger;", 93351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann false); 94e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 95e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[2]: Ljava/util/logging/Logger; 96e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[1]: [Ljava/lang/Object; 97e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Ljava/lang/Object; 98e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 99e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitInsn(Opcodes.SWAP); 100e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 101e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[2]: [Ljava/lang/Object; 102e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[1]: Ljava/util/logging/Logger; 103e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Ljava/lang/Object; 104e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 105e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitFieldInsn(Opcodes.GETSTATIC, "java/util/logging/Level", "INFO", 106e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov "Ljava/util/logging/Level;"); 107e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 108e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[3]: Ljava/util/logging/Level; 109e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[2]: [Ljava/lang/Object; 110e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[1]: Ljava/util/logging/Logger; 111e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Ljava/lang/Object; 112e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 113e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitInsn(Opcodes.SWAP); 114e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 115e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[3]: [Ljava/lang/Object; 116e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[2]: Ljava/util/logging/Level; 117e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[1]: Ljava/util/logging/Logger; 118e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Ljava/lang/Object; 119e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 120e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitLdcInsn(key); 121e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 122e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[4]: Ljava/lang/String; 123e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[3]: [Ljava/lang/Object; 124e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[2]: Ljava/util/logging/Level; 125e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[1]: Ljava/util/logging/Logger; 126e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Ljava/lang/Object; 127e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 128e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitInsn(Opcodes.SWAP); 129e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 130e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[4]: [Ljava/lang/Object; 131e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[3]: Ljava/lang/String; 132e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[2]: Ljava/util/logging/Level; 133e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[1]: Ljava/util/logging/Logger; 134e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Ljava/lang/Object; 135e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 136351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann mv.visitMethodInsn( 137351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann Opcodes.INVOKEVIRTUAL, 138351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann "java/util/logging/Logger", 139e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov "log", 140351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann "(Ljava/util/logging/Level;Ljava/lang/String;[Ljava/lang/Object;)V", 141351b20ee84db15e054b01c3a5e7b4234cecad890Marc R. Hoffmann false); 142e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 143e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Ljava/lang/Object; 144e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 145e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // 3. Load data structure from parameter array: 146e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 147e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitInsn(Opcodes.ICONST_0); 148e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitInsn(Opcodes.AALOAD); 149e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov mv.visitTypeInsn(Opcodes.CHECKCAST, InstrSupport.DATAFIELD_DESC); 150e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 151e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // Stack[0]: [Z 152e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 153e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov return 5; // Maximum local stack size is 5 154e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 155e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 15627e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann @Override 15727e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann public void startup(final RuntimeData data) throws Exception { 15827e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann super.startup(data); 159e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov this.logger.addHandler(handler); 160e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 161e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 162e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov public void shutdown() { 163e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov this.logger.removeHandler(handler); 164e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 165e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 166e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov private class RuntimeHandler extends Handler { 167e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 168e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov @Override 169e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov public void publish(final LogRecord record) { 170e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov if (key.equals(record.getMessage())) { 17127e32e433ba0ac1fdec9290d3454d5b369c969ccMarc R. Hoffmann data.getProbes(record.getParameters()); 172e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 173e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 174e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 175e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov @Override 176e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov public void flush() { 177e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // nothing to do 178e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 179e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 180e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov @Override 181e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov public void close() throws SecurityException { 182e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // The Java logging framework removes and closes all handlers on JVM 183e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // shutdown. As soon as our handler has been removed, all classes 184e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // that might get instrumented during shutdown (e.g. loaded by other 185e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // shutdown hooks) will fail to initialize. Therefore we add ourself 186e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // again here. This is a nasty hack that might fail in some Java 187e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov // implementations. 188e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov logger.addHandler(handler); 189e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 190e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov } 191e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov 192e69ba4dbb015949c5d84ba7bbb0b53efac28bb23Evgeny Mandrikov} 193