[JAVA] J'ai vérifié le nom du package référencé par la classe contenue dans le fichier JAR

Aperçu

Dans cette entrée, je laisserai une note de ce que j'ai essayé:

--Comment vérifier le contenu lorsqu'un fichier JAR Java est passé en binaire uniquement

supposition

La source

Ce que j'ai écrit est GitHub [https://github.com/hrkt/gohatto-j/releases/tag/0.0.1-qiita](https://github.com/hrkt/gohatto-j/releases/tag/ Je l'ai mis dans 0.0.1-qiita).

Scène supposée

―― Le lecteur est en charge de l'enseignement de la programmation en Java, et le stagiaire doit soumettre un fichier JAR de l'exercice et vérifier le fonctionnement.

Ce que j'ai essayé

Jetez un œil au contenu du fichier JAR

Il existe une classe appelée JarFile, donc j'utiliserai ce pouvoir.

L'énumération peut être obtenue avec entries (), donc le contenu sera déterminé dans l'ordre. Il est pratique que vous n'ayez pas à écrire vous-même le processus de redémarrage du répertoire.

        try (JarFile jarFile = new JarFile(jarFilePath)) {
            val e = jarFile.entries();

            val urls = new URL[]{new URL("jar:file:" + jarFilePath + "!/")};
            val ucl = URLClassLoader.newInstance(urls);
            val repository = new ClassLoaderRepository(ucl);

            return Collections.list(e).stream()
                    .filter(je -> !je.isDirectory())
                    .filter(je -> je.getName().endsWith(".class"))
                    .flatMap(je -> check(repository, je).stream())

Examinez le code dans le fichier de classe avec BCEL

BCEL

La fonction Java standard Reflection nécessite ce temps. Vous ne pouvez pas obtenir d'informations telles que la classe à laquelle le code dans le fichier de classe (après avoir été compilé en code d'octet) fait référence.

Par conséquent, nous utiliserons Apache Commons BCEL, une bibliothèque pour gérer le code d'octet Java.

Utilisez la classe Commons JavaClass pour charger la classe obtenue à partir du chargeur de classe Java ci-dessus et regardez le bytecode qu'il contient. Plus précisément, BCEL's "ConstantPool" (= fichier de classe analysé Examinez la chaîne contenue dans la liste des constantes contenues dans).

Code de test conçu pour la détection

J'ai écrit le code suivant, créé un fichier jar et l'ai essayé.

package io.hrkt;


import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("message", "Hello");
        System.out.println(map.get("message"));
    }
}

À ce stade, si vous excluez le pool de constantes dans BCEL, vous pouvez obtenir la liste suivante.

CONSTANT_Methodref[10](class_index=12,name_and_type_index=30)
CONSTANT_Class[7](name_index=31)
CONSTANT_Methodref[10](class_index=2,name_and_type_index=30)
CONSTANT_String[8](string_index=32)
CONSTANT_String[8](string_index=33)
CONSTANT_InterfaceMethodref[11](class_index=34,name_and_type_index=35)
CONSTANT_Fieldref[9](class_index=36,name_and_type_index=37)
CONSTANT_InterfaceMethodref[11](class_index=34,name_and_type_index=38)
CONSTANT_Class[7](name_index=39)
CONSTANT_Methodref[10](class_index=40,name_and_type_index=41)
CONSTANT_Class[7](name_index=42)
CONSTANT_Class[7](name_index=43)
CONSTANT_Utf8[1]("<init>")
CONSTANT_Utf8[1]("()V")
CONSTANT_Utf8[1]("Code")
CONSTANT_Utf8[1]("LineNumberTable")
CONSTANT_Utf8[1]("LocalVariableTable")
CONSTANT_Utf8[1]("this")
CONSTANT_Utf8[1]("Lio/hrkt/Main;")
CONSTANT_Utf8[1]("main")
CONSTANT_Utf8[1]("([Ljava/lang/String;)V")
CONSTANT_Utf8[1]("args")
CONSTANT_Utf8[1]("[Ljava/lang/String;")
CONSTANT_Utf8[1]("map")
CONSTANT_Utf8[1]("Ljava/util/Map;")
CONSTANT_Utf8[1]("LocalVariableTypeTable")
CONSTANT_Utf8[1]("Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;")
CONSTANT_Utf8[1]("SourceFile")
CONSTANT_Utf8[1]("Main.java")
CONSTANT_NameAndType[12](name_index=13,signature_index=14)
CONSTANT_Utf8[1]("java/util/HashMap")
CONSTANT_Utf8[1]("message")
CONSTANT_Utf8[1]("Hello")
CONSTANT_Class[7](name_index=44)
CONSTANT_NameAndType[12](name_index=45,signature_index=46)
CONSTANT_Class[7](name_index=47)
CONSTANT_NameAndType[12](name_index=48,signature_index=49)
CONSTANT_NameAndType[12](name_index=50,signature_index=51)
CONSTANT_Utf8[1]("java/lang/String")
CONSTANT_Class[7](name_index=52)
CONSTANT_NameAndType[12](name_index=53,signature_index=54)
CONSTANT_Utf8[1]("io/hrkt/Main")
CONSTANT_Utf8[1]("java/lang/Object")
CONSTANT_Utf8[1]("java/util/Map")
CONSTANT_Utf8[1]("put")
CONSTANT_Utf8[1]("(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")
CONSTANT_Utf8[1]("java/lang/System")
CONSTANT_Utf8[1]("out")
CONSTANT_Utf8[1]("Ljava/io/PrintStream;")
CONSTANT_Utf8[1]("get")
CONSTANT_Utf8[1]("(Ljava/lang/Object;)Ljava/lang/Object;")
CONSTANT_Utf8[1]("java/io/PrintStream")
CONSTANT_Utf8[1]("println")
CONSTANT_Utf8[1]("(Ljava/lang/String;)V")

code

Parmi les chaînes de caractères incluses dans ConstantPool, celles dont la balise indique un fichier de classe sont extraites.

        JavaClass clazz = null;
        try {
            clazz = repository.loadClass(fqcn);
            val cp = clazz.getConstantPool();
            int cpLength = cp.getLength();

            val referencedClasses = new ArrayList<ConstantUtf8>();
            IntStream.range(0, cpLength).forEach(i -> {
                val constant = cp.getConstant(i);
                if (null == constant || !(constant instanceof ConstantClass)) {
                    // do nothing
                } else {
                    val constantClass = (ConstantClass) constant;
                    val referencedConstant = cp.getConstant(constantClass.getNameIndex());
                    referencedClasses.add((ConstantUtf8) referencedConstant);
                }
            });
            val tc = referencedClasses.stream()
                    .map(c -> c.getBytes())
                    .map(s -> s.replace("/", "."))
                    .collect(Collectors.toList());
            return tc;
        } catch (ClassNotFoundException e) {
            throw new GohattoApplicationException(e);
        }

Règles d'inspection

Cette fois, il est déterminé en le faisant correspondre avec le nom du package inclus dans le fichier de paramètres de format de texte.

Comme vous pouvez le voir en regardant le code, il s'agit simplement d'une comparaison utilisant des expressions régulières.

modèle 1

Si vous préparez un fichier comme celui-ci, il sera détecté comme une erreur lorsque "java.util ou com.sun est utilisé".

java.util
com.sun

Motif 2

Si vous préparez un fichier comme celui-ci, il sera détecté comme une erreur "lorsque vous utilisez autre chose que java.lang ou java.io".

^(?!java.lang|java.io)

Exemple d'exécution

Voici un exemple d'utilisation de la source dans le référentiel.

En tant que commande

Lorsqu'il est exécuté en tant que commande, s'il y a du code qui n'autorise pas la référence au package Java, un journal d'erreurs comme celui illustré ci-dessous sera généré sur la sortie standard.

$ java -jar build/libs/gohatto-j-0.0.1-SNAPSHOT-all.jar --jar src/test/resources/testproject.jar --forbiddenPackageList  src/test/resources/rule_forbid_jav-util_and_com-sun.txt
[main] INFO io.hrkt.gohatto.Gohatto - ERROR     DoNotUseForbiddenPackage        java.util.HashMap violates rule
[main] INFO io.hrkt.gohatto.Gohatto - ERROR     DoNotUseForbiddenPackage        java.util.Map violates rule
[main] INFO io.hrkt.gohatto.Gohatto - rules applied to: 2

Dans JUnit

Dans la classe "Gohatto" dans le référentiel mentionné ci-dessus, qui est l'entrée de la fonction de vérification,

        String resDir = "src/test/resources/";
        Gohatto gohatto = new Gohatto();
        gohatto.init(resDir + "testproject.jar", resDir + "rule_forbid_jav-util_and_com-sun.txt");
        List<Result> ret = gohatto.executeRules();
        Assert.assertTrue(ret.size() == 0);

Résumé

Je vous ai montré comment lire le fichier JAR et vérifier le package de la classe précédente référencée par la classe qu'il contient.

Le code de travail est stocké dans le référentiel. Veuillez vous y référer le cas échéant.

Recommended Posts

J'ai vérifié le nom du package référencé par la classe contenue dans le fichier JAR
Renommez le nom du package d'un fichier jar existant
[Gradle] L'histoire selon laquelle le fichier de classe n'existait pas dans le fichier jar
Ajouter un horodatage au nom de fichier JAR dans Gradle
Trier par plusieurs champs dans la classe
Empêcher les erreurs dans le nom de l'enregistreur en copiant
Quand tu te perds dans le nom de la classe
Nommer et appeler explicitement des méthodes de classe parent dans Ruby
Obtenez le nom du scénario de test dans la classe de test JUnit
Inclure le code source généré par Doma dans le JAR source
[Java] Récupère le fichier dans le fichier jar quel que soit l'environnement
Comment déboguer le fichier jar généré avec Eclipse
Je veux donner un nom de classe à l'attribut select
Je souhaite rechercher de manière récursive la liste des classes sous le package
Je suis resté coincé dans le fichier
Je ne peux pas créer une classe Java avec un nom spécifique dans IntelliJ
Utilisez un exemple de domaine pour le nom du package dans l'exemple de code
Mettez la date (par exemple aaaa-MM-jj) dans la sortie du fichier journal par logback
Fixez le nom du fichier de guerre à celui défini dans Maven