blob: 1e6a84477257abaab7e629ddac54cb5542a85165 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.simulator.asm;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;
import static java.util.Arrays.stream;
import static org.apache.cassandra.simulator.asm.MethodLogger.Level.NONE;
import static org.apache.cassandra.simulator.asm.MethodLogger.Level.valueOf;
// TODO (config): support logging only for packages/classes matching a pattern
interface MethodLogger
{
static final Level LOG = valueOf(System.getProperty("cassandra.test.simulator.print_asm", "none").toUpperCase());
static final Set<TransformationKind> KINDS = System.getProperty("cassandra.test.simulator.print_asm_opts", "").isEmpty()
? EnumSet.allOf(TransformationKind.class)
: stream(System.getProperty("cassandra.test.simulator.print_asm_opts", "").split(","))
.map(TransformationKind::valueOf)
.collect(() -> EnumSet.noneOf(TransformationKind.class), Collection::add, Collection::addAll);
static final Pattern LOG_CLASSES = System.getProperty("cassandra.test.simulator.print_asm_classes", "").isEmpty()
? null
: Pattern.compile(System.getProperty("cassandra.test.simulator.print_asm_classes", ""));
// debug the output of each class at most once
static final Set<String> LOGGED_CLASS = LOG != NONE ? Collections.newSetFromMap(new ConcurrentHashMap<>()) : null;
enum Level { NONE, CLASS_SUMMARY, CLASS_DETAIL, METHOD_SUMMARY, METHOD_DETAIL, ASM }
MethodVisitor visitMethod(int access, String name, String descriptor, MethodVisitor parent);
void witness(TransformationKind kind);
void visitEndOfClass();
static MethodLogger log(int api, String className)
{
switch (LOG)
{
default:
case NONE:
return None.INSTANCE;
case ASM:
return (LOG_CLASSES == null || LOG_CLASSES.matcher(className).matches()) && LOGGED_CLASS.add(className)
? new Printing(api, className) : None.INSTANCE;
case CLASS_DETAIL:
case CLASS_SUMMARY:
case METHOD_DETAIL:
case METHOD_SUMMARY:
return (LOG_CLASSES == null || LOG_CLASSES.matcher(className).matches()) && LOGGED_CLASS.add(className)
? new Counting(api, className, LOG) : None.INSTANCE;
}
}
static class None implements MethodLogger
{
static final None INSTANCE = new None();
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, MethodVisitor parent)
{
return parent;
}
@Override
public void witness(TransformationKind kind)
{
}
@Override
public void visitEndOfClass()
{
}
}
static class Counting implements MethodLogger
{
final int api;
final String className;
final Level level;
StringWriter buffer = new StringWriter();
PrintWriter out = new PrintWriter(buffer);
boolean isMethodInProgress;
boolean printMethod;
boolean printClass;
int methodCount;
final int[] methodCounts = new int[TransformationKind.VALUES.size()];
final int[] classCounts = new int[TransformationKind.VALUES.size()];
public Counting(int api, String className, Level level)
{
this.api = api;
this.className = className;
this.level = level;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, MethodVisitor parent)
{
++methodCount;
if (isMethodInProgress)
return parent;
return new MethodVisitor(api, parent) {
@Override
public void visitEnd()
{
super.visitEnd();
if (printMethod)
{
for (int i = 0 ; i < methodCounts.length ; ++i)
classCounts[i] += methodCounts[i];
switch (level)
{
case METHOD_DETAIL:
out.printf("Transformed %s.%s %s\n", className, name, descriptor);
for (int i = 0 ; i < methodCounts.length ; ++i)
{
if (methodCounts[i] > 0)
out.printf(" %3d %s\n", methodCounts[i], TransformationKind.VALUES.get(i));
}
break;
case METHOD_SUMMARY:
out.printf("Transformed %s.%s %s with %d modifications\n", className, name, descriptor, stream(methodCounts).sum());
break;
}
printMethod = false;
Arrays.fill(methodCounts, 0);
}
isMethodInProgress = false;
}
};
}
public void visitEndOfClass()
{
if (!printClass)
return;
switch (level)
{
case CLASS_DETAIL:
out.printf("Transformed %s: %d methods\n", className, methodCount);
for (int i = 0 ; i < classCounts.length ; ++i)
{
if (classCounts[i] > 0)
out.printf(" %3d %s\n", classCounts[i], TransformationKind.VALUES.get(i));
}
case CLASS_SUMMARY:
out.printf("Transformed %s: %d methods with %d modifications\n", className, methodCount, stream(classCounts).sum());
}
System.out.print(buffer.toString());
buffer = null;
out = null;
}
@Override
public void witness(TransformationKind kind)
{
++methodCounts[kind.ordinal()];
if (KINDS.contains(kind))
{
printMethod = true;
printClass = true;
}
}
}
static class Printing implements MethodLogger
{
final int api;
final String className;
final Textifier textifier = new Textifier();
StringWriter buffer = new StringWriter();
PrintWriter out = new PrintWriter(buffer);
boolean printClass;
boolean printMethod;
boolean isMethodInProgress;
public Printing(int api, String className)
{
this.api = api;
this.className = className;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, MethodVisitor parent)
{
Printer printer = textifier.visitMethod(access, name, descriptor, null, null);
boolean isOuter = !isMethodInProgress;
if (isOuter) isMethodInProgress = true;
return new TraceMethodVisitor(new MethodVisitor(api, parent) {
@Override
public void visitEnd()
{
super.visitEnd();
if (printMethod)
{
out.println("====" + className + '.' + name + ' ' + descriptor + ' ');
printer.print(out);
}
if (isOuter) isMethodInProgress = false;
}
}, printer);
}
@Override
public void witness(TransformationKind kind)
{
if (KINDS.contains(kind))
{
printMethod = true;
printClass = true;
}
}
@Override
public void visitEndOfClass()
{
if (printClass)
System.out.println(buffer.toString());
buffer = null;
out = null;
}
}
}