Skip to content

Commit

Permalink
feat(#768): verify classes only when all of them were generated and r…
Browse files Browse the repository at this point in the history
…emove deprecated classes
  • Loading branch information
volodya-lombrozo committed Oct 19, 2024
1 parent e9cf4e6 commit 252c3b7
Show file tree
Hide file tree
Showing 7 changed files with 22 additions and 166 deletions.
3 changes: 1 addition & 2 deletions src/main/java/org/eolang/jeo/AssembleMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ public void execute() throws MojoExecutionException {
} else {
new Assembler(
this.sourcesDir.toPath(),
this.outputDir.toPath(),
false
this.outputDir.toPath()
).assemble();
if (this.skipVerification) {
Logger.info(this, "Verification is disabled. Skipping.");
Expand Down
11 changes: 2 additions & 9 deletions src/main/java/org/eolang/jeo/Assembler.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,14 @@ final class Assembler {
*/
private final Path output;

/**
* Whether to verify the assembled classes.
*/
private final boolean verify;

/**
* Constructor.
* @param input Input folder with "xmir" files.
* @param output Output folder for the assembled classes.
* @param verify Whether to verify the assembled classes.
*/
Assembler(final Path input, final Path output, final boolean verify) {
Assembler(final Path input, final Path output) {
this.input = input;
this.output = output;
this.verify = verify;
}

/**
Expand All @@ -80,7 +73,7 @@ void assemble() {
new Assemble(this.output)
)
)
).apply(new XmirRepresentations(this.input, this.verify).all());
).apply(new XmirRepresentations(this.input).all());
stream.forEach(
terminated -> Logger.info(
this,
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/eolang/jeo/BytecodeDirectory.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ void verify() {
}
}

/**
* Read bytes of the class file.
* @param clazz Class file to read.
* @return Bytes of the class file.
*/
private byte[] read(final Path clazz) {
try {
return Files.readAllBytes(clazz);
Expand All @@ -90,6 +95,10 @@ private byte[] read(final Path clazz) {
}
}

/**
* Verify the generated bytecode.
* @param bytes Bytecode to verify.
*/
private void verify(final byte[] bytes) {
final ClassNode clazz = new ClassNode();
new ClassReader(bytes)
Expand Down
18 changes: 1 addition & 17 deletions src/main/java/org/eolang/jeo/XmirRepresentations.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,12 @@ final class XmirRepresentations implements Representations {
*/
private final Path objectspath;

/**
* Verify bytecode.
* @since 0.2
*/
private final boolean verify;

/**
* Constructor.
* @param objectspath Where to read objects from.
*/
XmirRepresentations(final Path objectspath) {
this(objectspath, true);
}

/**
* Constructor.
* @param objectspath Where to read objects from.
* @param verify Verify bytecode.
*/
XmirRepresentations(final Path objectspath, final boolean verify) {
this.objectspath = objectspath;
this.verify = verify;
}

@Override
Expand All @@ -74,7 +58,7 @@ public Stream<? extends Representation> all() {
try {
return Files.walk(path)
.filter(Files::isRegularFile)
.map(p -> new XmirRepresentation(p, this.verify));
.map(XmirRepresentation::new);
} catch (final IOException exception) {
throw new IllegalStateException(
String.format("Can't read folder '%s'", path),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,27 +54,12 @@ public final class XmirRepresentation implements Representation {
*/
private final String source;

/**
* Verify bytecode.
* @since 0.2
*/
private final boolean verify;

/**
* Constructor.
* @param path Path to XML file.
*/
public XmirRepresentation(final Path path) {
this(path, true);
}

/**
* Constructor.
* @param path Path to XML file.
* @param verify Verify bytecode.
*/
public XmirRepresentation(final Path path, final boolean verify) {
this(XmirRepresentation.fromFile(path), path.toAbsolutePath().toString(), verify);
this(XmirRepresentation.fromFile(path), path.toAbsolutePath().toString());
}

/**
Expand All @@ -94,37 +79,20 @@ public XmirRepresentation(
final XML xml,
final String source
) {
this(xml, source, true);
}

/**
* Constructor.
* @param xml XML.
* @param source Source of the XML.
* @param verify Verify bytecode.
*/
public XmirRepresentation(
final XML xml,
final String source,
final boolean verify
) {
this(new Unchecked<>(() -> xml), source, verify);
this(new Unchecked<>(() -> xml), source);
}

/**
* Constructor.
* @param xml XML source.
* @param source Source of the XML.
* @param verify Verify bytecode.
*/
private XmirRepresentation(
final Unchecked<XML> xml,
final String source,
final boolean verify
final String source
) {
this.xml = xml;
this.source = source;
this.verify = verify;
}

@Override
Expand All @@ -142,7 +110,7 @@ public Bytecode toBytecode() {
final XML xmir = this.xml.value();
try {
new Schema(xmir).check();
return new XmlProgram(xmir).bytecode().bytecode(this.verify);
return new XmlProgram(xmir).bytecode().bytecode();
} catch (final IllegalArgumentException exception) {
throw new IllegalArgumentException(
String.format("Can't transform '%s' to bytecode", xmir),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,28 +103,11 @@ public XML xml() {

/**
* Generate bytecode.
* <p>
* In this method we intentionally use the Thread.currentThread().getContextClassLoader()
* for the class loader of the
* {@link CheckClassAdapter#verify(ClassReader, ClassLoader, boolean, PrintWriter)}
* instead of default implementation. This is because the default class loader doesn't
* know about classes compiled on the previous maven step.
* You can read more about the problem here:
* {@link PluginStartup#init()} ()}
* </p>
* @return Bytecode.
*/
public Bytecode bytecode() {
return this.bytecode(true);
}

/**
* Traverse XML and build bytecode class.
* @param verify Verify bytecode.
* @return Bytecode.
*/
public Bytecode bytecode(final boolean verify) {
final CustomClassWriter writer = new CustomClassWriter(verify);
public Bytecode bytecode() {
final CustomClassWriter writer = new CustomClassWriter();
this.top().writeTo(writer, this.pckg);
return writer.bytecode();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,13 @@
package org.eolang.jeo.representation.bytecode;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.eolang.jeo.PluginStartup;
import org.eolang.jeo.representation.DefaultVersion;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.SimpleVerifier;
import org.objectweb.asm.util.CheckClassAdapter;

/**
* Custom class writer.
Expand All @@ -60,10 +48,9 @@ public final class CustomClassWriter extends ClassVisitor {

/**
* Constructor.
* @param verify Verify bytecode.
*/
CustomClassWriter(final boolean verify) {
this(CustomClassWriter.prestructor(verify));
CustomClassWriter() {
this(new ClassesAwareWriter());
}

/**
Expand Down Expand Up @@ -167,21 +154,6 @@ private MethodVisitor visitMethodWithoutFrames(
}
}

/**
* Which class writer to use.
* @param verify Verify bytecode.
* @return A verified class writer if verify is true, otherwise custom class writer.
*/
private static ClassesAwareWriter prestructor(final boolean verify) {
final ClassesAwareWriter result;
if (verify) {
result = new VerifiedClassWriter();
} else {
result = new ClassesAwareWriter();
}
return result;
}

/**
* Class writer that knows about additional classes loaded.
* This class works in couple with {@link PluginStartup#init()} ()} method that sets
Expand All @@ -206,7 +178,7 @@ private static class ClassesAwareWriter extends ClassWriter {
* Constructor.
* Do not compute frames automatically.
*/
private ClassesAwareWriter() {
ClassesAwareWriter() {
this(0);
}

Expand All @@ -223,56 +195,4 @@ public final ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
}

/**
* Class writer that verifies the bytecode.
* @since 0.2
*/
@ToString
@EqualsAndHashCode(callSuper = false)
private static final class VerifiedClassWriter extends ClassesAwareWriter {

@Override
public byte[] toByteArray() {
final byte[] bytes = super.toByteArray();
VerifiedClassWriter.verify(bytes);
return bytes;
}

/**
* Verify the bytecode.
* @param bytes The bytecode to verify.
*/
private static void verify(final byte[] bytes) {
final ClassNode clazz = new ClassNode();
new ClassReader(bytes)
.accept(new CheckClassAdapter(clazz, false), ClassReader.SKIP_DEBUG);
final Optional<Type> syper = Optional.ofNullable(clazz.superName)
.map(Type::getObjectType);
final List<Type> interfaces = clazz.interfaces.stream().map(Type::getObjectType)
.collect(Collectors.toList());
for (final MethodNode method : clazz.methods) {
try {
final SimpleVerifier verifier =
new SimpleVerifier(
Type.getObjectType(clazz.name),
syper.orElse(null),
interfaces,
(clazz.access & Opcodes.ACC_INTERFACE) != 0
);
verifier.setClassLoader(Thread.currentThread().getContextClassLoader());
new Analyzer<>(verifier).analyze(clazz.name, method);
} catch (final ClassFormatError | AnalyzerException exception) {
throw new IllegalStateException(
String.format(
"Bytecode verification failed for the class '%s' and method '%s'",
clazz.name,
method.name
),
exception
);
}
}
}
}
}

0 comments on commit 252c3b7

Please sign in to comment.