/*
* Copyright 2016 NAVER Corp.
*
* 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.navercorp.pinpoint.profiler.instrument;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentContext;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
/**
* @author jaehong.kim
*/
public final class ASMClassWriter extends ClassWriter {
private static final String OBJECT_CLASS_INTERNAL_NAME = "java/lang/Object";
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final InstrumentContext pluginContext;
private ClassLoader classLoader;
private String classInternalName;
private String superClassInternalName;
public ASMClassWriter(final InstrumentContext pluginContext, final String classInternalName, final String superClassInternalName, final int flags, final ClassLoader classLoader) {
super(flags);
this.pluginContext = pluginContext;
this.classInternalName = classInternalName;
this.superClassInternalName = superClassInternalName;
this.classLoader = classLoader;
}
@Override
protected String getCommonSuperClass(String type1ClassInternalName, String type2ClassInternalName) {
if (logger.isDebugEnabled()) {
logger.debug("Getting common super class. type1ClassInternalName={}, type2ClassInternalName={}", type1ClassInternalName, type2ClassInternalName);
}
final String classInternalName = get(type1ClassInternalName, type2ClassInternalName);
if (logger.isDebugEnabled()) {
logger.debug("Common super class is '{}'. type1ClassInternalName={}, type2ClassInternalName={}", classInternalName, type1ClassInternalName, type2ClassInternalName);
}
return classInternalName;
}
private String get(final String type1ClassInternalName, final String type2ClassInternalName) {
if (type1ClassInternalName == null || type1ClassInternalName.equals(OBJECT_CLASS_INTERNAL_NAME) || type2ClassInternalName == null || type2ClassInternalName.equals(OBJECT_CLASS_INTERNAL_NAME)) {
// object is the root of the class hierarchy.
return OBJECT_CLASS_INTERNAL_NAME;
}
if (type1ClassInternalName.equals(type2ClassInternalName)) {
// two equal.
return type1ClassInternalName;
}
// current class.
if (type1ClassInternalName.equals(classInternalName)) {
return getCommonSuperClass(superClassInternalName, type2ClassInternalName);
} else if (type2ClassInternalName.equals(classInternalName)) {
return getCommonSuperClass(type1ClassInternalName, superClassInternalName);
}
ClassReader type1ClassReader = getClassReader(type1ClassInternalName);
ClassReader type2ClassReader = getClassReader(type2ClassInternalName);
if (type1ClassReader == null || type2ClassReader == null) {
logger.warn("Skip get common super class. not found class {type1ClassInternalName={}, reader={}}, {type2ClassInternalName={}, reader={}}", type1ClassInternalName, type1ClassReader, type2ClassInternalName, type2ClassReader);
return OBJECT_CLASS_INTERNAL_NAME;
}
// interface.
if (isInterface(type1ClassReader)) {
String interfaceInternalName = type1ClassInternalName;
if (isImplements(interfaceInternalName, type2ClassReader)) {
return interfaceInternalName;
}
if (isInterface(type2ClassReader)) {
interfaceInternalName = type2ClassInternalName;
if (isImplements(interfaceInternalName, type1ClassReader)) {
return interfaceInternalName;
}
}
return OBJECT_CLASS_INTERNAL_NAME;
}
// interface.
if (isInterface(type2ClassReader)) {
String interfaceName = type2ClassInternalName;
if (isImplements(interfaceName, type1ClassReader)) {
return interfaceName;
}
return OBJECT_CLASS_INTERNAL_NAME;
}
// class.
final Set<String> superClassNames = new HashSet<String>();
superClassNames.add(type1ClassInternalName);
superClassNames.add(type2ClassInternalName);
String type1SuperClassName = type1ClassReader.getSuperName();
if (!superClassNames.add(type1SuperClassName)) {
// find common superClass.
return type1SuperClassName;
}
String type2SuperClassName = type2ClassReader.getSuperName();
if (!superClassNames.add(type2SuperClassName)) {
// find common superClass.
return type2SuperClassName;
}
while (type1SuperClassName != null || type2SuperClassName != null) {
if (type1SuperClassName != null) {
type1SuperClassName = getSuperClassInternalName(type1SuperClassName);
if (type1SuperClassName != null) {
if (!superClassNames.add(type1SuperClassName)) {
return type1SuperClassName;
}
}
}
if (type2SuperClassName != null) {
type2SuperClassName = getSuperClassInternalName(type2SuperClassName);
if (type2SuperClassName != null) {
if (!superClassNames.add(type2SuperClassName)) {
return type2SuperClassName;
}
}
}
}
return OBJECT_CLASS_INTERNAL_NAME;
}
private boolean isInterface(final ClassReader classReader) {
return (classReader.getAccess() & Opcodes.ACC_INTERFACE) != 0;
}
private boolean isImplements(final String interfaceInternalName, final ClassReader classReader) {
ClassReader classInfo = classReader;
while (classInfo != null) {
final String[] interfaceInternalNames = classInfo.getInterfaces();
for (String name : interfaceInternalNames) {
if (name != null && name.equals(interfaceInternalName)) {
return true;
}
}
for (String name : interfaceInternalNames) {
if(name != null) {
final ClassReader interfaceInfo = getClassReader(name);
if (interfaceInfo != null) {
if (isImplements(interfaceInternalName, interfaceInfo)) {
return true;
}
}
}
}
final String superClassInternalName = classInfo.getSuperName();
if (superClassInternalName == null || superClassInternalName.equals(OBJECT_CLASS_INTERNAL_NAME)) {
break;
}
classInfo = getClassReader(superClassInternalName);
}
return false;
}
private String getSuperClassInternalName(final String classInternalName) {
final ClassReader classReader = getClassReader(classInternalName);
if (classReader == null) {
return null;
}
return classReader.getSuperName();
}
private ClassReader getClassReader(final String classInternalName) {
InputStream in = null;
try {
in = pluginContext.getResourceAsStream(this.classLoader, classInternalName + ".class");
if (in != null) {
return new ClassReader(in);
}
} catch (IOException ignored) {
// not found class.
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignored) {
}
}
}
return null;
}
}