I checked the package name referenced by the class contained in the JAR file

Overview

In this entry, I'll leave a note of what I've tried:

--How to check the contents when a Java JAR file is passed in binary only --How to check the package name of the referenced class from the state of being a class file

Premise

--Those who have basic knowledge about Java -Those who know lombok (so those who are not surprised even if a final declaration with type inference called "val" appears on the left side)

Source

What I wrote is GitHub [https://github.com/hrkt/gohatto-j/releases/tag/0.0.1-qiita](https://github.com/hrkt/gohatto-j/releases/tag/ I put it in 0.0.1-qiita).

Assumed scene

――The reader is in charge of programming education using Java, and the trainee needs to submit the JAR file of the exercise and check the operation. ――It's a task that I want you to practice basic data structures such as lists, and it's kind of subtle if java.util.ArrayList is used. .. So I want to check it --Assuming that there is no source, as you can see by greping the source

What I tried

Take a look at the contents of the JAR file

There is a class called JarFile, so I will use this power.

Enumeration can be obtained by entries (), so the contents will be determined in order. It's convenient that you don't have to write the directory recovery process yourself.

        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())

Examine the code in the class file with BCEL

BCEL

The Java standard Reflection function requires this time. You can't get information such as the class referenced by the code in the class file (after being compiled into bytecode).

Therefore, we use Apache Commons BCEL, a library for handling Java bytecode.

Use the Commons JavaClass class to load the class obtained from the Java class loader above and look at the bytecode in it. Specifically, BCEL's "ConstantPool" (= parsed class file Examine the string contained in (list of constants contained in).

Test code made for detection

I wrote the following code, made a jar file, and tried it.

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"));
    }
}

At this time, if you exclude Constant Pool in BCEL, you can get the following list.

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

Of the strings included in ConstantPool, the one whose Tag indicates the class file is extracted.

        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);
        }

Inspection rules

This time, it is determined by matching it with the package name included in the text format configuration file.

As you can see from the code, it's just a regular expression comparison.

pattern 1

If you prepare a file like this, it will be detected as an error when "when using java.util or com.sun".

java.util
com.sun

Pattern 2

If you prepare a file like this, it will be detected as an error when "when using something other than java.lang or java.io".

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

Execution example

Here is an example of using the source in the repository.

As a command

When executed as a command, if there is an unacceptable Java package reference code, an error log as shown below will be output to the standard output.

$ 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

In JUnit

In the "Gohatto" class in the repository mentioned above, which is the entrance to the check function,

        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);

Summary

I showed you how to read the JAR file and check the package of the previous class referenced by the class in it.

The working code is stored in the repository. Please refer to it as appropriate.

Recommended Posts

I checked the package name referenced by the class contained in the JAR file
Rename the package name of the existing jar file
[Gradle] The story that the class file did not exist in the jar file
Add a time stamp to the JAR file name in Gradle
Sort by multiple fields in the class
Preventing mistakes in the Logger name by copying
When you get lost in the class name
Call a method of the parent class by explicitly specifying the name in Ruby
Get the name of the test case in the JUnit test class
Include the source code generated by Doma in the source JAR
[Java] Get the file in the jar regardless of the environment
How to debug the generated jar file in Eclipse
I want to give a class name to the select attribute
I want to recursively search the class list under the package
About the File :: Stat class
I got stuck in File
I can't create a Java class with a specific name in IntelliJ
Use an example domain for the package name in the sample code
Put the date (e.g. yyyy-MM-dd) in the log file output by logback
Fix the file name of war to the one set in Maven