/*
* Copyright 2012-2017 the original author or authors.
*
* 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 org.glowroot.agent.weaving.preinit;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import org.objectweb.asm.ClassReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GlobalCollector {
private static final Logger logger = LoggerFactory.getLogger(GlobalCollector.class);
// caches
private final Set<ReferencedMethod> referencedMethods = Sets.newHashSet();
private final Map<String, Optional<ClassCollector>> classCollectors = Maps.newHashMap();
private final Set<ReferencedMethod> overrides = Sets.newTreeSet();
private String indent = "";
public void processMethodFailIfNotFound(ReferencedMethod rootMethod) throws IOException {
processMethod(rootMethod, true);
}
public void processMethod(ReferencedMethod rootMethod) throws IOException {
String prevIndent = indent;
indent = indent + " ";
processMethod(rootMethod, false);
indent = prevIndent;
}
public void registerClass(String internalName) throws IOException {
addClass(internalName);
}
public void processOverrides() throws IOException {
while (true) {
for (String internalName : classCollectors.keySet()) {
addOverrideReferencedMethods(internalName);
addOverrideBootstrapMethods(internalName);
}
if (overrides.isEmpty()) {
return;
}
for (ReferencedMethod override : overrides) {
logger.debug("{} (processing overrides)", override);
processMethod(override);
}
overrides.clear();
}
}
public List<String> usedInternalNames() {
List<String> internalNames = Lists.newArrayList();
for (String internalName : Ordering.natural().sortedCopy(classCollectors.keySet())) {
if (!InternalNames.inBootstrapClassLoader(internalName)
&& !internalName.startsWith("org/slf4j/")
&& !internalName.startsWith("org/glowroot/agent/shaded/slf4j/")
&& InternalNames.exists(internalName)) {
internalNames.add(internalName.replace('/', '.'));
}
}
return internalNames;
}
private void processMethod(ReferencedMethod rootMethod, boolean expected) throws IOException {
if (referencedMethods.contains(rootMethod)) {
return;
}
String owner = rootMethod.getOwner();
if (owner.startsWith("[")) {
// method on an Array, e.g. new String[] {}.clone()
return;
}
logger.debug("{}{}", indent, rootMethod);
// add the containing class and its super classes if not already added
Optional<ClassCollector> optional = classCollectors.get(owner);
if (optional == null) {
optional = addClass(owner);
}
if (!optional.isPresent()) {
// couldn't find class
if (expected) {
throw new IOException("Could not find class: " + owner);
} else {
return;
}
}
referencedMethods.add(rootMethod);
if (InternalNames.inBootstrapClassLoader(owner)) {
return;
}
if (owner.startsWith("org/slf4j/")
|| owner.startsWith("org/glowroot/agent/shaded/slf4j/")) {
return;
}
if (rootMethod.getOwner().startsWith("org/glowroot/transaction/model/Transaction")
&& rootMethod.getName().equals("toString")
&& rootMethod.getDesc().equals("()Ljava/lang/String;")) {
// special case since Transaction.toString() would otherwise pull in many other classes
// but it only exists for debugging so no need to worry about these classes
return;
}
ClassCollector classCollector = optional.get();
String methodId = rootMethod.getName() + ":" + rootMethod.getDesc();
MethodCollector methodCollector = classCollector.getMethodCollector(methodId);
if (expected && methodCollector == null) {
throw new IOException("Could not find method: " + rootMethod);
}
if (methodCollector == null && !rootMethod.getName().equals("<clinit>")
&& classCollector.getSuperInternalNames() != null) {
// can't find method in class, so go up to super class
processMethod(
ReferencedMethod.create(classCollector.getSuperInternalNames(), methodId));
}
// methodCollector can be null, e.g. unimplemented interface method in an abstract class
if (methodCollector != null) {
processMethod(methodCollector);
}
}
private void processMethod(MethodCollector methodCollector) throws IOException {
// add classes referenced from inside the method
for (String referencedInternalName : methodCollector.getReferencedInternalNames()) {
addClass(referencedInternalName);
}
// recurse into other methods called from inside the method
for (ReferencedMethod referencedMethod : methodCollector.getReferencedMethods()) {
processMethod(referencedMethod);
}
}
private Optional<ClassCollector> addClass(String internalName) throws IOException {
Optional<ClassCollector> optional = classCollectors.get(internalName);
if (optional != null) {
return optional;
}
List<String> allSuperInternalNames = Lists.newArrayList();
ClassCollector classCollector = createClassCollector(internalName);
if (classCollector == null) {
optional = Optional.absent();
classCollectors.put(internalName, optional);
return optional;
}
// don't return or recurse without classCollector being fully built
classCollectors.put(internalName, Optional.of(classCollector));
if (classCollector.getSuperInternalNames() != null) {
// it's a major problem if super class is not present, ok to call Optional.get()
ClassCollector superClassCollector =
addClass(classCollector.getSuperInternalNames()).get();
allSuperInternalNames.addAll(superClassCollector.getAllSuperInternalNames());
allSuperInternalNames.add(classCollector.getSuperInternalNames());
}
for (String interfaceInternalName : classCollector.getInterfaceInternalNames()) {
Optional<ClassCollector> loopOptional = addClass(interfaceInternalName);
if (loopOptional.isPresent()) {
allSuperInternalNames.addAll(loopOptional.get().getAllSuperInternalNames());
allSuperInternalNames.add(interfaceInternalName);
} else {
logger.debug("could not find class: {}", interfaceInternalName);
classCollector.setAllSuperInternalNames(allSuperInternalNames);
return Optional.absent();
}
}
classCollector.setAllSuperInternalNames(allSuperInternalNames);
// add static initializer (if it exists)
processMethod(ReferencedMethod.create(internalName, "<clinit>", "()V"));
return Optional.of(classCollector);
}
private @Nullable ClassCollector createClassCollector(String internalName) {
if (ClassLoader.getSystemResource(internalName + ".class") == null) {
// no need to log error for H2 optional geometry support
if (!internalName.startsWith("com/vividsolutions/jts/")) {
logger.error("could not find class: {}", internalName);
}
return null;
}
ClassCollector classCollector = new ClassCollector();
try {
ClassReader cr = new ClassReader(internalName);
MyRemappingClassAdapter visitor = new MyRemappingClassAdapter(classCollector);
cr.accept(visitor, 0);
return classCollector;
} catch (IOException e) {
logger.error("error parsing class: {}", internalName);
return null;
}
}
private void addOverrideReferencedMethods(String internalName) {
Optional<ClassCollector> optional = classCollectors.get(internalName);
if (!optional.isPresent()) {
return;
}
ClassCollector classCollector = optional.get();
for (String methodId : classCollector.getMethodIds()) {
if (methodId.startsWith("<clinit>:") || methodId.startsWith("<init>:")) {
continue;
}
for (String superInternalName : classCollector.getAllSuperInternalNames()) {
if (referencedMethods
.contains(ReferencedMethod.create(superInternalName, methodId))) {
addOverrideMethod(internalName, methodId);
// break inner loop
break;
}
}
}
}
private void addOverrideBootstrapMethods(String internalName) {
if (InternalNames.inBootstrapClassLoader(internalName)) {
return;
}
Optional<ClassCollector> optional = classCollectors.get(internalName);
if (!optional.isPresent()) {
return;
}
ClassCollector classCollector = optional.get();
// add overridden bootstrap methods in class, e.g. hashCode(), toString()
for (String methodId : classCollector.getMethodIds()) {
if (methodId.startsWith("<clinit>:") || methodId.startsWith("<init>:")) {
continue;
}
for (String superInternalName : classCollector.getAllSuperInternalNames()) {
if (InternalNames.inBootstrapClassLoader(superInternalName)) {
ClassCollector superClassCollector =
classCollectors.get(superInternalName).get();
if (superClassCollector.getMethodCollector(methodId) != null) {
addOverrideMethod(internalName, methodId);
}
}
}
}
}
private void addOverrideMethod(String internalName, String methodId) {
ReferencedMethod referencedMethod = ReferencedMethod.create(internalName, methodId);
if (!referencedMethods.contains(referencedMethod)) {
overrides.add(referencedMethod);
}
}
}