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
--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)
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).
――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
There is a class called JarFile, so I will use this power.
Enumeration
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())
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).
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")
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);
}
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.
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
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)
Here is an example of using the source in the repository.
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 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);
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