All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.spotify.missinglink.ClassLoader Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2015 Spotify AB
 *
 * 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.spotify.missinglink;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;

import com.spotify.missinglink.datamodel.AccessedField;
import com.spotify.missinglink.datamodel.AccessedFieldBuilder;
import com.spotify.missinglink.datamodel.CalledMethod;
import com.spotify.missinglink.datamodel.CalledMethodBuilder;
import com.spotify.missinglink.datamodel.ClassTypeDescriptor;
import com.spotify.missinglink.datamodel.DeclaredClass;
import com.spotify.missinglink.datamodel.DeclaredClassBuilder;
import com.spotify.missinglink.datamodel.DeclaredField;
import com.spotify.missinglink.datamodel.DeclaredFieldBuilder;
import com.spotify.missinglink.datamodel.DeclaredMethod;
import com.spotify.missinglink.datamodel.DeclaredMethodBuilder;
import com.spotify.missinglink.datamodel.MethodDescriptor;
import com.spotify.missinglink.datamodel.MethodDescriptors;
import com.spotify.missinglink.datamodel.TypeDescriptors;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static java.util.stream.Collectors.toList;

/**
 * TODO: document!
 */
public class ClassLoader {

  private final InputStream in;

  public ClassLoader(InputStream in) {
    this.in = in;
  }

  public DeclaredClass load() throws IOException {
    ClassReader reader = new ClassReader(in);
    DeclaredClassBuilder builder = new DeclaredClassBuilder();

    final String className = reader.getClassName();
    builder.className(TypeDescriptors.fromClassName(className));
    final ClassNode classNode = new ClassNode();
    reader.accept(classNode, 0);

    ImmutableSet.Builder fields = new ImmutableSet.Builder<>();

    @SuppressWarnings("unchecked")
    final Iterable classFields = (Iterable) classNode.fields;
    for (FieldNode field : classFields) {
      fields.add(new DeclaredFieldBuilder()
          .name(field.name)
          .descriptor(TypeDescriptors.fromRaw(field.desc))
          .build());
    }

    final Map declaredMethods = Maps.newHashMap();

    for (MethodNode method : ClassLoader.uncheckedCast(classNode.methods)) {
      // ... and the InsnList type looks like a java.util.List but is not one because why not?
      final Set thisCalls = new HashSet<>();
      final Set thisFields = new HashSet<>();

      int lineNumber = 0;
      for (Iterator it2 =
           ClassLoader.uncheckedCast(method.instructions.iterator());
           it2.hasNext();) {
        final AbstractInsnNode insn = it2.next();
        if (insn instanceof LineNumberNode) {
          lineNumber = ((LineNumberNode) insn).line;
        }
        if (insn instanceof MethodInsnNode) {
          final MethodInsnNode minsn = (MethodInsnNode) insn;

          if (minsn.owner.charAt(0) == '[') {
            // TODO: add this as some other type of analysis instead of method call (class reference?)
            //ArrayTypeDescriptor arrayDescriptor = (ArrayTypeDescriptor) TypeDescriptors.fromRaw(minsn.owner);

          } else {
            thisCalls.add(new CalledMethodBuilder()
                .owner(TypeDescriptors.fromClassName(minsn.owner))
                .descriptor(MethodDescriptors.fromDesc(minsn.desc, minsn.name))
                .lineNumber(lineNumber)
                .build());

          }
        }
        if (insn instanceof FieldInsnNode) {
          final FieldInsnNode finsn = (FieldInsnNode) insn;
          if (finsn.owner.charAt(0) != '[') {
            thisFields.add(
                new AccessedFieldBuilder()
                    .name(finsn.name)
                    .descriptor(TypeDescriptors.fromRaw(finsn.desc))
                    .owner(TypeDescriptors.fromClassName(finsn.owner))
                    .lineNumber(lineNumber)
                    .build());
          }
        }
      }

      final DeclaredMethod declaredMethod = new DeclaredMethodBuilder()
          .descriptor(MethodDescriptors.fromDesc(method.desc, method.name))
          .methodCalls(ImmutableSet.copyOf(thisCalls))
          .fieldAccesses(ImmutableSet.copyOf(thisFields))
          .build();

      if (declaredMethods.put(declaredMethod.descriptor(), declaredMethod) != null) {
        throw new RuntimeException(
            "Multiple definitions of " + declaredMethod.descriptor() + " in class " + className);
      }
    }

    final Set parents = new HashSet<>();
    parents.addAll(ClassLoader.uncheckedCast(classNode.interfaces)
        .stream()
        .map(TypeDescriptors::fromClassName)
        .collect(toList()));
    // java/lang/Object has no superclass
    if (classNode.superName != null) {
      parents.add(TypeDescriptors.fromClassName(classNode.superName));
    }

    builder.methods(ImmutableMap.copyOf(declaredMethods))
        .parents(ImmutableSet.copyOf(parents))
        .fields(fields.build());

    return builder.build();
  }

  // asm seems to compile it's code with a very low source version, so all collections from it
  // are unchecked types. These helper functions at least suppress the warnings for us:
  //
  @SuppressWarnings("unchecked")
  private static  List uncheckedCast(List list) {
    return (List) list;
  }

  @SuppressWarnings("unchecked")
  private static  Iterator uncheckedCast(Iterator iterator) {
    return (Iterator) iterator;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy