From 671852be4459acc3f09db91d5a6d77d1f8b4b1fd Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:05:45 +0000 Subject: [PATCH 1/8] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e3d9c8a..2486cde 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/9A22t-SS) Разработать standalone приложение, которое имеет следующие возможности: Принимает на вход проект в виде .jar файла From 28d5199943716acd90cafa0384dacbc902057b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=94=D0=B0=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=BE=D0=B2?= Date: Tue, 2 Dec 2025 23:42:56 +0300 Subject: [PATCH 2/8] =?UTF-8?q?=D0=9D=D0=B0=D0=B8=D0=B2=D0=BD=D0=B0=D1=8F?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=81=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=BD=D0=B0=20@Override?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 3 + src/main/java/org/example/App.java | 40 ++ src/main/java/org/example/Example.java | 29 -- .../java/org/example/analyzer/Analyzer.java | 91 ++++ .../org/example/analyzer/ClassMetrics.java | 9 + .../java/org/example/analyzer/JarMetrics.java | 9 + .../example/analyzer/visitor/ABCVisitor.java | 115 +++++ .../analyzer/visitor/InheritanceVisitor.java | 85 ++++ .../java/org/example/example/BubbleSort.java | 29 -- .../org/example/util/ByteCodePrinter.java | 90 ---- .../org/example/util/CheckFrameAnalyzer.java | 475 ------------------ .../org/example/visitor/ClassPrinter.java | 46 -- 12 files changed, 352 insertions(+), 669 deletions(-) create mode 100644 src/main/java/org/example/App.java delete mode 100644 src/main/java/org/example/Example.java create mode 100644 src/main/java/org/example/analyzer/Analyzer.java create mode 100644 src/main/java/org/example/analyzer/ClassMetrics.java create mode 100644 src/main/java/org/example/analyzer/JarMetrics.java create mode 100644 src/main/java/org/example/analyzer/visitor/ABCVisitor.java create mode 100644 src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java delete mode 100644 src/main/java/org/example/example/BubbleSort.java delete mode 100644 src/main/java/org/example/util/ByteCodePrinter.java delete mode 100644 src/main/java/org/example/util/CheckFrameAnalyzer.java delete mode 100644 src/main/java/org/example/visitor/ClassPrinter.java diff --git a/build.gradle.kts b/build.gradle.kts index 2b92afd..feb920a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,9 @@ dependencies { implementation("org.ow2.asm:asm-analysis:9.5") implementation("org.ow2.asm:asm-util:9.5") + implementation("com.fasterxml.jackson.core:jackson-core:2.20.1") + implementation("com.fasterxml.jackson.core:jackson-databind:2.20.1") + testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java new file mode 100644 index 0000000..0279634 --- /dev/null +++ b/src/main/java/org/example/App.java @@ -0,0 +1,40 @@ +package org.example; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.example.analyzer.Analyzer; +import org.example.analyzer.JarMetrics; + +public class App { + private static final String defaultJar = "src/main/resources/sample.jar"; + private static final String defaultOutput = null; + + public static void main(String[] args) throws IOException { + String jar = defaultJar; + String outFile = defaultOutput; + + if (args.length > 0) { + jar = args[0]; + } + + if (args.length > 1) { + outFile = args[1]; + } + + JarMetrics metrics = new Analyzer().analyze(jar); + ObjectMapper mapper = new ObjectMapper(); + byte[] stringifiedMetrics = mapper.writeValueAsBytes(metrics); + System.out.write(stringifiedMetrics); + System.out.println(); + + if (outFile != null) { + try (OutputStream outputStream = new FileOutputStream(outFile)) { + outputStream.write(stringifiedMetrics); + } + } + } +} diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java deleted file mode 100644 index 52d0abe..0000000 --- a/src/main/java/org/example/Example.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.example; - -import org.example.visitor.ClassPrinter; -import org.objectweb.asm.ClassReader; - -import java.io.IOException; -import java.util.Enumeration; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -public class Example { - - public static void main(String[] args) throws IOException { -// var printer = new ByteCodePrinter(); -// printer.printBubbleSortBytecode(); - try (JarFile sampleJar = new JarFile("src/main/resources/sample.jar")) { - Enumeration enumeration = sampleJar.entries(); - - while (enumeration.hasMoreElements()) { - JarEntry entry = enumeration.nextElement(); - if (entry.getName().endsWith(".class")) { - ClassPrinter cp = new ClassPrinter(); - ClassReader cr = new ClassReader(sampleJar.getInputStream(entry)); - cr.accept(cp, 0); - } - } - } - } -} diff --git a/src/main/java/org/example/analyzer/Analyzer.java b/src/main/java/org/example/analyzer/Analyzer.java new file mode 100644 index 0000000..f42b5a5 --- /dev/null +++ b/src/main/java/org/example/analyzer/Analyzer.java @@ -0,0 +1,91 @@ +package org.example.analyzer; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarFile; + +import org.example.analyzer.visitor.ABCVisitor; +import org.example.analyzer.visitor.InheritanceVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; + +public class Analyzer { + private Map classMetircsMap = new HashMap<>(); + + private InheritanceVisitor inheritanceVisitor = new InheritanceVisitor(); + + public JarMetrics analyze(String jarPath) { + try (JarFile jar = new JarFile(jarPath)) { + jar.stream() + .filter(entry -> entry.getName().endsWith(".class")) + .forEach(entry -> { + try (InputStream is = jar.getInputStream(entry)) { + ClassReader reader = new ClassReader(is); + processClass(reader); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + + return calcMetrics(); + } + + private void processClass(ClassReader reader) { + reader.accept(inheritanceVisitor, 0); + + ClassNode node = new ClassNode(); + reader.accept(node, 0); + + ABCVisitor abcVisitor = new ABCVisitor(); + reader.accept(abcVisitor, 0); + + ClassMetrics classMetrics = new ClassMetrics(); + + classMetrics.assignments = abcVisitor.getAssignments(); + classMetrics.branches = abcVisitor.getBranches(); + classMetrics.conditionals = abcVisitor.getConditionals(); + classMetrics.overrides = (int) node.fields.stream().filter((fieldNode) -> { + if (fieldNode.visibleAnnotations == null) { + return false; + } + + return fieldNode.visibleAnnotations.stream() + .anyMatch((annotationNode) -> annotationNode.desc.equals("Ljava/lang/Override;")); + }).count(); + classMetrics.fields = node.fields.size(); + + classMetircsMap.put(node.name, classMetrics); + } + + private JarMetrics calcMetrics() { + Long totalAssigments = Long.valueOf(0); + Long totalBranches = Long.valueOf(0); + Long totalConditions = Long.valueOf(0); + + Integer totalOverrides = 0; + Integer totalFields = 0; + + for (ClassMetrics classMetrics : classMetircsMap.values()) { + totalOverrides += classMetrics.overrides; + totalFields += classMetrics.fields; + totalAssigments += classMetrics.assignments; + totalBranches += classMetrics.branches; + totalConditions += classMetrics.conditionals; + } + + Double ABC = Math.sqrt( + totalAssigments * totalAssigments + totalBranches * totalBranches + totalConditions + totalConditions); + + return new JarMetrics( + inheritanceVisitor.getMaxInheritanceDepth(), + inheritanceVisitor.getAvgInheritanceDepth(), + ABC, + Double.valueOf(totalOverrides) / classMetircsMap.size(), + Double.valueOf(totalFields) / classMetircsMap.size()); + } +} diff --git a/src/main/java/org/example/analyzer/ClassMetrics.java b/src/main/java/org/example/analyzer/ClassMetrics.java new file mode 100644 index 0000000..2af8126 --- /dev/null +++ b/src/main/java/org/example/analyzer/ClassMetrics.java @@ -0,0 +1,9 @@ +package org.example.analyzer; + +class ClassMetrics { + Long assignments; + Long branches; + Long conditionals; + Integer overrides; + Integer fields; +} \ No newline at end of file diff --git a/src/main/java/org/example/analyzer/JarMetrics.java b/src/main/java/org/example/analyzer/JarMetrics.java new file mode 100644 index 0000000..89a9da6 --- /dev/null +++ b/src/main/java/org/example/analyzer/JarMetrics.java @@ -0,0 +1,9 @@ +package org.example.analyzer; + +public record JarMetrics( + Integer maxInheritanceDepth, + Double avgInheritanceDepth, + Double ABC, + Double avgOverrides, + Double avgClassFields) { +} diff --git a/src/main/java/org/example/analyzer/visitor/ABCVisitor.java b/src/main/java/org/example/analyzer/visitor/ABCVisitor.java new file mode 100644 index 0000000..0a3df09 --- /dev/null +++ b/src/main/java/org/example/analyzer/visitor/ABCVisitor.java @@ -0,0 +1,115 @@ +package org.example.analyzer.visitor; + +import static org.objectweb.asm.Opcodes.*; + +import java.util.Set; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +public class ABCVisitor extends ClassVisitor { + public ABCVisitor() { + super(ASM8); + } + + private Long assignments = Long.valueOf(0); + private Long branches = Long.valueOf(0); + private Long conditionals = Long.valueOf(0); + + public Long getAssignments() { + return assignments; + } + + public Long getBranches() { + return branches; + } + + public Long getConditionals() { + return conditionals; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new ABCMethodVisitor(); + } + + class ABCMethodVisitor extends MethodVisitor { + private static final Set assignmentOpCodes = Set.of( + IASTORE, LASTORE, FASTORE, DASTORE, + AASTORE, BASTORE, CASTORE, SASTORE, + PUTSTATIC, PUTFIELD); + + public ABCMethodVisitor() { + super(ASM8); + } + + @Override + public void visitIincInsn(final int varIndex, final int increment) { + assignments++; + } + + @Override + public void visitVarInsn(final int opcode, final int varIndex) { + if (assignmentOpCodes.contains(opcode)) { + assignments++; + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + if (opcode == NEW) { + branches++; + } + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor) { + if (assignmentOpCodes.contains(opcode)) { + assignments++; + } + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + if (opcode != GOTO) { + conditionals++; + } + } + + @Override + public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { + conditionals++; + } + + @Override + public void visitInvokeDynamicInsn( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + branches++; + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + branches++; + } + + @Override + public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) { + conditionals += labels.length + 1; + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + conditionals += labels.length + 1; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java b/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java new file mode 100644 index 0000000..99687d5 --- /dev/null +++ b/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java @@ -0,0 +1,85 @@ +package org.example.analyzer.visitor; + +import static org.objectweb.asm.Opcodes.ASM8; + +import java.util.HashMap; +import java.util.Map; + +import org.objectweb.asm.ClassVisitor; + +public class InheritanceVisitor extends ClassVisitor { + private class ClassInfo { + String name; + String superName; + + Integer inharitanceDepth; + + ClassInfo(String name, String superName) { + this.name = name; + this.superName = superName; + + inharitanceDepth = -1; + } + } + + private final Map classesInfoMap = new HashMap<>(); + + public InheritanceVisitor() { + super(ASM8); + } + + private Integer calcInharitanceDepth(String name) { + String currentClass = name; + Integer depth = 0; + + while (true) { + if (currentClass.equals("java/lang/Object")) { + break; + } + + ClassInfo classInfo = classesInfoMap.get(currentClass); + if (classInfo.inharitanceDepth > -1) { + depth += classInfo.inharitanceDepth; + break; + } + + depth++; + currentClass = classInfo.superName; + } + + return depth; + } + + public Integer getMaxInheritanceDepth() { + Integer maxDepth = -1; + for (ClassInfo classInfo : classesInfoMap.values()) { + if (classInfo.inharitanceDepth == -1) { + classInfo.inharitanceDepth = calcInharitanceDepth(classInfo.name); + } + + maxDepth = Math.max(maxDepth, classInfo.inharitanceDepth); + } + return maxDepth; + } + + public Double getAvgInheritanceDepth() { + Integer depthSum = 0; + for (ClassInfo classInfo : classesInfoMap.values()) { + if (classInfo.inharitanceDepth == -1) { + classInfo.inharitanceDepth = calcInharitanceDepth(classInfo.name); + } + + depthSum += classInfo.inharitanceDepth; + } + return Double.valueOf(depthSum) / classesInfoMap.size(); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if (superName == null) { + return; + } + + classesInfoMap.putIfAbsent(name, new ClassInfo(name, superName)); + } +} diff --git a/src/main/java/org/example/example/BubbleSort.java b/src/main/java/org/example/example/BubbleSort.java deleted file mode 100644 index 8a21c95..0000000 --- a/src/main/java/org/example/example/BubbleSort.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.example.example; - -public class BubbleSort { - - static void bubbleSort(int[] arr, int n) - { - int i, j, temp; - boolean swapped; - for (i = 0; i < n - 1; i++) { - swapped = false; - for (j = 0; j < n - i - 1; j++) { - if (arr[j] > arr[j + 1]) { - - // Swap arr[j] and arr[j+1] - temp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = temp; - swapped = true; - } - } - - // If no two elements were - // swapped by inner loop, then break - if (!swapped) - break; - } - } - -} diff --git a/src/main/java/org/example/util/ByteCodePrinter.java b/src/main/java/org/example/util/ByteCodePrinter.java deleted file mode 100644 index 3d1ca38..0000000 --- a/src/main/java/org/example/util/ByteCodePrinter.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.example.util; - -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.TryCatchBlockNode; -import org.objectweb.asm.tree.analysis.*; -import org.objectweb.asm.util.Textifier; -import org.objectweb.asm.util.TraceMethodVisitor; - -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Path; - -public class ByteCodePrinter { - - private static String getUnqualifiedName(final String name) { - var lastSlashIndex = name.lastIndexOf('/'); - if (lastSlashIndex == -1) { - return name; - } else { - int endIndex = name.length(); - if (name.charAt(endIndex - 1) == ';') { - endIndex--; - } - int lastBracketIndex = name.lastIndexOf('['); - if (lastBracketIndex == -1) { - return name.substring(lastSlashIndex + 1, endIndex); - } - return name.substring(0, lastBracketIndex + 1) + name.substring(lastSlashIndex + 1, endIndex); - } - } - private static void analyzeMethod( - final MethodNode method, final Analyzer analyzer, final PrintWriter printWriter) { - var textifier = new Textifier(); - var traceMethodVisitor = new TraceMethodVisitor(textifier); - - printWriter.println(method.name + method.desc); - for (int i = 0; i < method.instructions.size(); ++i) { - method.instructions.get(i).accept(traceMethodVisitor); - - var stringBuilder = new StringBuilder(); - var frame = analyzer.getFrames()[i]; - if (frame == null) { - stringBuilder.append('?'); - } else { - for (int j = 0; j < frame.getLocals(); ++j) { - stringBuilder.append(getUnqualifiedName(frame.getLocal(j).toString())).append(' '); - } - stringBuilder.append(" : "); - for (int j = 0; j < frame.getStackSize(); ++j) { - stringBuilder.append(getUnqualifiedName(frame.getStack(j).toString())).append(' '); - } - } - while (stringBuilder.length() < method.maxStack + method.maxLocals + 1) { - stringBuilder.append(' '); - } - printWriter.print(Integer.toString(i + 100000).substring(1)); - printWriter.print( - " " + stringBuilder + " : " + textifier.text.get(textifier.text.size() - 1)); - } - for (TryCatchBlockNode tryCatchBlock : method.tryCatchBlocks) { - tryCatchBlock.accept(traceMethodVisitor); - printWriter.print(" " + textifier.text.get(textifier.text.size() - 1)); - } - printWriter.println(); - } - - public void printBytecode(ClassNode cn) { - var sortMethod = cn.methods.get(1); - var analyzer = new CheckFrameAnalyzer<>(new BasicVerifier()); - try { - analyzer.analyze("dummy", sortMethod); - } catch (AnalyzerException e) { - throw new RuntimeException(e); - } - var pw = new PrintWriter(System.out); - analyzeMethod(sortMethod, analyzer, pw); - pw.flush(); - } - - public void printBubbleSortBytecode() throws IOException { - var cn = new ClassNode(); - var classFileBytes = Files.readAllBytes(Path.of("build/classes/java/main/org/itmo/lab1/example/BubbleSort.class")); - var classReader = new ClassReader(classFileBytes); - classReader.accept(cn, ClassReader.EXPAND_FRAMES); - printBytecode(cn); - } -} diff --git a/src/main/java/org/example/util/CheckFrameAnalyzer.java b/src/main/java/org/example/util/CheckFrameAnalyzer.java deleted file mode 100644 index 5231ca7..0000000 --- a/src/main/java/org/example/util/CheckFrameAnalyzer.java +++ /dev/null @@ -1,475 +0,0 @@ -package org.example.util; - -// ASM: a very small and fast Java bytecode manipulation framework -// Copyright (c) 2000-2011 INRIA, France Telecom -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// 1. Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// 3. Neither the name of the copyright holders nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -// THE POSSIBILITY OF SUCH DAMAGE. - -import java.util.Collections; -import java.util.List; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FrameNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.InsnNode; -import org.objectweb.asm.tree.JumpInsnNode; -import org.objectweb.asm.tree.LabelNode; -import org.objectweb.asm.tree.LookupSwitchInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.TableSwitchInsnNode; -import org.objectweb.asm.tree.TryCatchBlockNode; -import org.objectweb.asm.tree.TypeInsnNode; -import org.objectweb.asm.tree.analysis.Analyzer; -import org.objectweb.asm.tree.analysis.AnalyzerException; -import org.objectweb.asm.tree.analysis.Frame; -import org.objectweb.asm.tree.analysis.Interpreter; -import org.objectweb.asm.tree.analysis.Value; - -/** - * An {@link Analyzer} subclass which checks that methods provide stack map frames where expected - * (i.e. at jump target and after instructions without immediate successor), and that these stack - * map frames are valid (for the provided interpreter; they may still be invalid for the JVM, if the - * {@link Interpreter} uses a simplified type system compared to the JVM verifier). This is done in - * two steps: - * - *
    - *
  • First, the stack map frames in {@link FrameNode}s are expanded, and stored at their - * respective instruction offsets. The expansion process uncompresses the APPEND, CHOP and - * SAME frames to FULL frames. It also converts the stack map frame verification types to - * {@link Value}s, via the provided {@link Interpreter}. The expansion is done in {@link - * #expandFrames}, by looking at each {@link FrameNode} in sequence (compressed frames are - * defined relatively to the previous {@link FrameNode}, or the implicit first frame). The - * actual decompression is done in {@link #expandFrame}, and the type conversion in {@link - * #newFrameValue}. - *
  • Next, the method instructions are checked in sequence. Starting from the implicit initial - * frame, the execution of each instruction i is simulated on the current stack map - * frame, with the {@link Frame#execute} method. This gives a new stack map frame f, - * representing the stack map frame state after the execution of i. Then: - *
      - *
    • If there is a next instruction and if the control flow cannot continue to it (e.g. if - * i is a RETURN or an ATHROW, for instance): an existing stack map frame - * f0 (coming from the first step) is expected after i. - *
    • If there is a next instruction and if the control flow can continue to it (e.g. if - * i is a ALOAD, for instance): either there an existing stack map frame - * f0 (coming from the first step) after i, or there is none. In the - * first case f and f0 must be compatible: the types in - * f must be sub types of the corresponding types in the existing frame - * f0 (otherwise an exception is thrown). In the second case, f0 is - * simply set to the value of f. - *
    • If the control flow can continue to some instruction j (e.g. if i - * is an IF_EQ, for instance): an existing stack map frame f0 (coming from the - * first step) is expected at j, which must be compatible with f (as - * defined previously). - *
    - * The sequential loop over the instructions is done in {@link #init}, which is called from - * the {@link Analyzer#analyze} method. Cases where the control flow cannot continue to the - * next instruction are handled in {@link #endControlFlow}. Cases where the control flow can - * continue to the next instruction, or jump to another instruction, are handled in {@link - * #checkFrame}. This method checks that an existing stack map frame is present when required, - * and checks the stack map frames compatibility with {@link #checkMerge}. - *
- * - * @author Eric Bruneton - * @param type of the {@link Value} used for the analysis. - */ -class CheckFrameAnalyzer extends Analyzer { - - /** The interpreter to use to symbolically interpret the bytecode instructions. */ - private final Interpreter interpreter; - - /** The instructions of the currently analyzed method. */ - private InsnList insnList; - - /** - * double values are represented with two elements. - */ - private int currentLocals; - - CheckFrameAnalyzer(final Interpreter interpreter) { - super(interpreter); - this.interpreter = interpreter; - } - - @Override - protected void init(final String owner, final MethodNode method) throws AnalyzerException { - insnList = method.instructions; - currentLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2; - - Frame[] frames = getFrames(); - Frame currentFrame = frames[0]; - expandFrames(owner, method, currentFrame); - for (int insnIndex = 0; insnIndex < insnList.size(); ++insnIndex) { - Frame oldFrame = frames[insnIndex]; - - // Simulate the execution of this instruction. - AbstractInsnNode insnNode = null; - try { - insnNode = method.instructions.get(insnIndex); - int insnOpcode = insnNode.getOpcode(); - int insnType = insnNode.getType(); - - if (insnType == AbstractInsnNode.LABEL - || insnType == AbstractInsnNode.LINE - || insnType == AbstractInsnNode.FRAME) { - checkFrame(insnIndex + 1, oldFrame, /* requireFrame = */ false); - } else { - currentFrame.init(oldFrame).execute(insnNode, interpreter); - - if (insnNode instanceof JumpInsnNode) { - if (insnOpcode == JSR) { - throw new AnalyzerException(insnNode, "JSR instructions are unsupported"); - } - JumpInsnNode jumpInsn = (JumpInsnNode) insnNode; - int targetInsnIndex = insnList.indexOf(jumpInsn.label); - checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true); - if (insnOpcode == GOTO) { - endControlFlow(insnIndex); - } else { - checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false); - } - } else if (insnNode instanceof LookupSwitchInsnNode) { - LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) insnNode; - int targetInsnIndex = insnList.indexOf(lookupSwitchInsn.dflt); - checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true); - for (int i = 0; i < lookupSwitchInsn.labels.size(); ++i) { - LabelNode label = lookupSwitchInsn.labels.get(i); - targetInsnIndex = insnList.indexOf(label); - currentFrame.initJumpTarget(insnOpcode, label); - checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true); - } - endControlFlow(insnIndex); - } else if (insnNode instanceof TableSwitchInsnNode) { - TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) insnNode; - int targetInsnIndex = insnList.indexOf(tableSwitchInsn.dflt); - currentFrame.initJumpTarget(insnOpcode, tableSwitchInsn.dflt); - checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true); - newControlFlowEdge(insnIndex, targetInsnIndex); - for (int i = 0; i < tableSwitchInsn.labels.size(); ++i) { - LabelNode label = tableSwitchInsn.labels.get(i); - currentFrame.initJumpTarget(insnOpcode, label); - targetInsnIndex = insnList.indexOf(label); - checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true); - } - endControlFlow(insnIndex); - } else if (insnOpcode == RET) { - throw new AnalyzerException(insnNode, "RET instructions are unsupported"); - } else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) { - checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false); - } else { - endControlFlow(insnIndex); - } - } - - List insnHandlers = getHandlers(insnIndex); - if (insnHandlers != null) { - for (TryCatchBlockNode tryCatchBlock : insnHandlers) { - Type catchType; - if (tryCatchBlock.type == null) { - catchType = Type.getObjectType("java/lang/Throwable"); - } else { - catchType = Type.getObjectType(tryCatchBlock.type); - } - Frame handler = newFrame(oldFrame); - handler.clearStack(); - handler.push(interpreter.newExceptionValue(tryCatchBlock, handler, catchType)); - checkFrame(insnList.indexOf(tryCatchBlock.handler), handler, /* requireFrame = */ true); - } - } - - if (!hasNextJvmInsnOrFrame(insnIndex)) { - break; - } - } catch (AnalyzerException e) { - throw new AnalyzerException( - e.node, "Error at instruction " + insnIndex + ": " + e.getMessage(), e); - } catch (RuntimeException e) { - // DontCheck(IllegalCatch): can't be fixed, for backward compatibility. - throw new AnalyzerException( - insnNode, "Error at instruction " + insnIndex + ": " + e.getMessage(), e); - } - } - } - - /** - * Expands the {@link FrameNode} "instructions" of the given method into {@link Frame} objects and - * also associated with the label and line number nodes immediately preceding each frame node. - * - * @param owner the internal name of the class to which 'method' belongs. - * @param method the method whose frames must be expanded. - * @param initialFrame the implicit initial frame of 'method'. - * @throws AnalyzerException if the stack map frames of 'method', i.e. its FrameNode - * "instructions", are invalid. - */ - private void expandFrames( - final String owner, final MethodNode method, final Frame initialFrame) - throws AnalyzerException { - int lastJvmOrFrameInsnIndex = -1; - Frame currentFrame = initialFrame; - int currentInsnIndex = 0; - for (AbstractInsnNode insnNode : method.instructions) { - if (insnNode instanceof FrameNode) { - try { - currentFrame = expandFrame(owner, currentFrame, (FrameNode) insnNode); - } catch (AnalyzerException e) { - throw new AnalyzerException( - e.node, "Error at instruction " + currentInsnIndex + ": " + e.getMessage(), e); - } - for (int index = lastJvmOrFrameInsnIndex + 1; index <= currentInsnIndex; ++index) { - getFrames()[index] = currentFrame; - } - } - if (isJvmInsnNode(insnNode) || insnNode instanceof FrameNode) { - lastJvmOrFrameInsnIndex = currentInsnIndex; - } - currentInsnIndex += 1; - } - } - - /** - * Returns the expanded representation of the given {@link FrameNode}. - * - * @param owner the internal name of the class to which 'frameNode' belongs. - * @param previousFrame the frame before 'frameNode', in expanded form. - * @param frameNode a possibly compressed stack map frame. - * @return the expanded version of 'frameNode'. - * @throws AnalyzerException if 'frameNode' is invalid. - */ - private Frame expandFrame( - final String owner, final Frame previousFrame, final FrameNode frameNode) - throws AnalyzerException { - Frame frame = newFrame(previousFrame); - List locals = frameNode.local == null ? Collections.emptyList() : frameNode.local; - int currentLocal = currentLocals; - switch (frameNode.type) { - case Opcodes.F_NEW: - case Opcodes.F_FULL: - currentLocal = 0; - // fall through - case Opcodes.F_APPEND: - for (Object type : locals) { - V value = newFrameValue(owner, frameNode, type); - if (currentLocal + value.getSize() > frame.getLocals()) { - throw new AnalyzerException(frameNode, "Cannot append more locals than maxLocals"); - } - frame.setLocal(currentLocal++, value); - if (value.getSize() == 2) { - frame.setLocal(currentLocal++, interpreter.newValue(null)); - } - } - break; - case Opcodes.F_CHOP: - for (Object unusedType : locals) { - if (currentLocal <= 0) { - throw new AnalyzerException(frameNode, "Cannot chop more locals than defined"); - } - if (currentLocal > 1 && frame.getLocal(currentLocal - 2).getSize() == 2) { - currentLocal -= 2; - } else { - currentLocal -= 1; - } - } - break; - case Opcodes.F_SAME: - case Opcodes.F_SAME1: - break; - default: - throw new AnalyzerException(frameNode, "Illegal frame type " + frameNode.type); - } - currentLocals = currentLocal; - while (currentLocal < frame.getLocals()) { - frame.setLocal(currentLocal++, interpreter.newValue(null)); - } - - List stack = frameNode.stack == null ? Collections.emptyList() : frameNode.stack; - frame.clearStack(); - for (Object type : stack) { - frame.push(newFrameValue(owner, frameNode, type)); - } - return frame; - } - - /** - * Creates a new {@link Value} that represents the given stack map frame type. - * - * @param owner the internal name of the class to which 'frameNode' belongs. - * @param frameNode the stack map frame to which 'type' belongs. - * @param type an Integer, String or LabelNode object representing a primitive, reference or - * uninitialized a stack map frame type, respectively. See {@link FrameNode}. - * @return a value that represents the given type. - * @throws AnalyzerException if 'type' is an invalid stack map frame type. - */ - private V newFrameValue(final String owner, final FrameNode frameNode, final Object type) - throws AnalyzerException { - if (type == Opcodes.TOP) { - return interpreter.newValue(null); - } else if (type == Opcodes.INTEGER) { - return interpreter.newValue(Type.INT_TYPE); - } else if (type == Opcodes.FLOAT) { - return interpreter.newValue(Type.FLOAT_TYPE); - } else if (type == Opcodes.LONG) { - return interpreter.newValue(Type.LONG_TYPE); - } else if (type == Opcodes.DOUBLE) { - return interpreter.newValue(Type.DOUBLE_TYPE); - } else if (type == Opcodes.NULL) { - return interpreter.newOperation(new InsnNode(Opcodes.ACONST_NULL)); - } else if (type == Opcodes.UNINITIALIZED_THIS) { - return interpreter.newValue(Type.getObjectType(owner)); - } else if (type instanceof String) { - return interpreter.newValue(Type.getObjectType((String) type)); - } else if (type instanceof LabelNode) { - AbstractInsnNode referencedNode = (LabelNode) type; - while (referencedNode != null && !isJvmInsnNode(referencedNode)) { - referencedNode = referencedNode.getNext(); - } - if (referencedNode == null || referencedNode.getOpcode() != Opcodes.NEW) { - throw new AnalyzerException(frameNode, "LabelNode does not designate a NEW instruction"); - } - return interpreter.newValue(Type.getObjectType(((TypeInsnNode) referencedNode).desc)); - } - throw new AnalyzerException(frameNode, "Illegal stack map frame value " + type); - } - - /** - * Checks that the given frame is compatible with the frame at the given instruction index, if - * any. If there is no frame at this instruction index and none is required, the frame at - * 'insnIndex' is set to the given frame. Otherwise, if the merge of the two frames is not equal - * to the current frame at 'insnIndex', an exception is thrown. - * - * @param insnIndex an instruction index. - * @param frame a frame. This frame is left unchanged by this method. - * 'insnIndex'. - * @throws AnalyzerException if the frames have incompatible sizes or if the frame at 'insnIndex' - * is missing (if required) or not compatible with 'frame'. - */ - private void checkFrame(final int insnIndex, final Frame frame, final boolean requireFrame) - throws AnalyzerException { - Frame oldFrame = getFrames()[insnIndex]; - if (oldFrame == null) { - if (requireFrame) { - throw new AnalyzerException(null, "Expected stack map frame at instruction " + insnIndex); - } - getFrames()[insnIndex] = newFrame(frame); - } else { - String error = checkMerge(frame, oldFrame); - if (error != null) { - throw new AnalyzerException( - null, - "Stack map frame incompatible with frame at instruction " - + insnIndex - + " (" - + error - + ")"); - } - } - } - - /** - * Checks that merging the two given frames would not produce any change, i.e. that the types in - * the source frame are sub types of the corresponding types in the destination frame. - * - * @param srcFrame a source frame. This frame is left unchanged by this method. - * @param dstFrame a destination frame. This frame is left unchanged by this method. - * @return an error message if the frames have incompatible sizes, or if a type in the source - * frame is not a sub type of the corresponding type in the destination frame. Returns - * {@literal null} otherwise. - */ - private String checkMerge(final Frame srcFrame, final Frame dstFrame) { - int numLocals = srcFrame.getLocals(); - if (numLocals != dstFrame.getLocals()) { - throw new AssertionError(); - } - for (int i = 0; i < numLocals; ++i) { - V v = interpreter.merge(srcFrame.getLocal(i), dstFrame.getLocal(i)); - if (!v.equals(dstFrame.getLocal(i))) { - return "incompatible types at local " - + i - + ": " - + srcFrame.getLocal(i) - + " and " - + dstFrame.getLocal(i); - } - } - int numStack = srcFrame.getStackSize(); - if (numStack != dstFrame.getStackSize()) { - return "incompatible stack heights"; - } - for (int i = 0; i < numStack; ++i) { - V v = interpreter.merge(srcFrame.getStack(i), dstFrame.getStack(i)); - if (!v.equals(dstFrame.getStack(i))) { - return "incompatible types at stack item " - + i - + ": " - + srcFrame.getStack(i) - + " and " - + dstFrame.getStack(i); - } - } - return null; - } - - /** - * Ends the control flow graph at the given instruction. This method checks that there is an - * existing frame for the next instruction, if any. - * - * @param insnIndex an instruction index. - * @throws AnalyzerException if 'insnIndex' is not the last instruction and there is no frame at - * 'insnIndex' + 1 in {@link #getFrames}. - */ - private void endControlFlow(final int insnIndex) throws AnalyzerException { - if (hasNextJvmInsnOrFrame(insnIndex) && getFrames()[insnIndex + 1] == null) { - throw new AnalyzerException( - null, "Expected stack map frame at instruction " + (insnIndex + 1)); - } - } - - /** - * Returns true if the given instruction is followed by a JVM instruction or a by stack map frame. - * - * @param insnIndex an instruction index. - * @return true if 'insnIndex' is followed by a JVM instruction or a by stack map frame. - */ - private boolean hasNextJvmInsnOrFrame(final int insnIndex) { - AbstractInsnNode insn = insnList.get(insnIndex).getNext(); - while (insn != null) { - if (isJvmInsnNode(insn) || insn instanceof FrameNode) { - return true; - } - insn = insn.getNext(); - } - return false; - } - - /** - * Returns true if the given instruction node corresponds to a real JVM instruction. - * - * @param insnNode an instruction node. - * @return true except for label, line number and stack map frame nodes. - */ - private static boolean isJvmInsnNode(final AbstractInsnNode insnNode) { - return insnNode.getOpcode() >= 0; - } -} diff --git a/src/main/java/org/example/visitor/ClassPrinter.java b/src/main/java/org/example/visitor/ClassPrinter.java deleted file mode 100644 index ba1ab9f..0000000 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.example.visitor; - -import org.objectweb.asm.*; - -import static org.objectweb.asm.Opcodes.ASM8; - -public class ClassPrinter extends ClassVisitor { - public ClassPrinter() { - super(ASM8); - } - - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - System.out.println("\n" + name + " extends " + superName + " {"); - } - - public void visitSource(String source, String debug) { - } - - public void visitOuterClass(String owner, String name, String desc) { - } - - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - return null; - } - - public void visitAttribute(Attribute attr) { - } - - public void visitInnerClass(String name, String outerName, String innerName, int access) { - } - - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - System.out.println(" " + desc + " " + name); - return null; - } - - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - System.out.println(" " + name + desc); - return null; - } - - public void visitEnd() { - System.out.println("}"); - } -} - From 5cdf7567b2ed724936e5832480fb3361a544b43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=94=D0=B0=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=BE=D0=B2?= Date: Thu, 4 Dec 2025 17:15:01 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=9F=D0=BE=D1=84=D0=B8=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=80=D0=B0=D1=81=D1=87=D0=B5=D1=82=20=D0=B3=D1=80?= =?UTF-8?q?=D1=83=D0=B1=D0=B8=D0=BD=D1=8B=20=D0=BD=D0=B0=D1=81=D0=BB=D0=B5?= =?UTF-8?q?=D0=B4=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/example/analyzer/visitor/InheritanceVisitor.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java b/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java index 99687d5..890b3c4 100644 --- a/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java +++ b/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java @@ -33,7 +33,7 @@ private Integer calcInharitanceDepth(String name) { Integer depth = 0; while (true) { - if (currentClass.equals("java/lang/Object")) { + if (currentClass == null) { break; } @@ -76,10 +76,6 @@ public Double getAvgInheritanceDepth() { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - if (superName == null) { - return; - } - classesInfoMap.putIfAbsent(name, new ClassInfo(name, superName)); } } From af9d50e7224566865f97b77909794305249ab339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=94=D0=B0=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=BE=D0=B2?= Date: Thu, 4 Dec 2025 17:48:12 +0300 Subject: [PATCH 4/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BB=D1=8E=D0=B4=D1=81=D0=BA=D0=BE=D0=B9=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D1=81=D1=87=D0=B5=D1=82=20override?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/example/analyzer/Analyzer.java | 7 +- .../analyzer/visitor/InheritanceVisitor.java | 6 +- .../analyzer/visitor/OverrideVisitor.java | 148 ++++++++++++++++++ 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/example/analyzer/visitor/OverrideVisitor.java diff --git a/src/main/java/org/example/analyzer/Analyzer.java b/src/main/java/org/example/analyzer/Analyzer.java index f42b5a5..fe466f9 100644 --- a/src/main/java/org/example/analyzer/Analyzer.java +++ b/src/main/java/org/example/analyzer/Analyzer.java @@ -8,6 +8,7 @@ import org.example.analyzer.visitor.ABCVisitor; import org.example.analyzer.visitor.InheritanceVisitor; +import org.example.analyzer.visitor.OverrideVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; @@ -15,6 +16,7 @@ public class Analyzer { private Map classMetircsMap = new HashMap<>(); private InheritanceVisitor inheritanceVisitor = new InheritanceVisitor(); + private OverrideVisitor overrideVisitor = new OverrideVisitor(); public JarMetrics analyze(String jarPath) { try (JarFile jar = new JarFile(jarPath)) { @@ -37,6 +39,7 @@ public JarMetrics analyze(String jarPath) { private void processClass(ClassReader reader) { reader.accept(inheritanceVisitor, 0); + reader.accept(overrideVisitor, 0); ClassNode node = new ClassNode(); reader.accept(node, 0); @@ -49,6 +52,7 @@ private void processClass(ClassReader reader) { classMetrics.assignments = abcVisitor.getAssignments(); classMetrics.branches = abcVisitor.getBranches(); classMetrics.conditionals = abcVisitor.getConditionals(); + classMetrics.overrides = (int) node.fields.stream().filter((fieldNode) -> { if (fieldNode.visibleAnnotations == null) { return false; @@ -57,6 +61,7 @@ private void processClass(ClassReader reader) { return fieldNode.visibleAnnotations.stream() .anyMatch((annotationNode) -> annotationNode.desc.equals("Ljava/lang/Override;")); }).count(); + classMetrics.fields = node.fields.size(); classMetircsMap.put(node.name, classMetrics); @@ -85,7 +90,7 @@ private JarMetrics calcMetrics() { inheritanceVisitor.getMaxInheritanceDepth(), inheritanceVisitor.getAvgInheritanceDepth(), ABC, - Double.valueOf(totalOverrides) / classMetircsMap.size(), + overrideVisitor.getAvgMethodOverrides(), Double.valueOf(totalFields) / classMetircsMap.size()); } } diff --git a/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java b/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java index 890b3c4..13315b4 100644 --- a/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java +++ b/src/main/java/org/example/analyzer/visitor/InheritanceVisitor.java @@ -8,7 +8,7 @@ import org.objectweb.asm.ClassVisitor; public class InheritanceVisitor extends ClassVisitor { - private class ClassInfo { + private static class ClassInfo { String name; String superName; @@ -38,6 +38,10 @@ private Integer calcInharitanceDepth(String name) { } ClassInfo classInfo = classesInfoMap.get(currentClass); + if (classInfo == null) { + break; + } + if (classInfo.inharitanceDepth > -1) { depth += classInfo.inharitanceDepth; break; diff --git a/src/main/java/org/example/analyzer/visitor/OverrideVisitor.java b/src/main/java/org/example/analyzer/visitor/OverrideVisitor.java new file mode 100644 index 0000000..bf8a128 --- /dev/null +++ b/src/main/java/org/example/analyzer/visitor/OverrideVisitor.java @@ -0,0 +1,148 @@ +package org.example.analyzer.visitor; + +import static org.objectweb.asm.Opcodes.ACC_INTERFACE; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ASM8; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +public class OverrideVisitor extends ClassVisitor { + public OverrideVisitor() { + super(ASM8); + } + + static class Method { + final String name; + final String argumentTypes; + boolean overrideFound = false; + + Method(String name, String argumentTypes) { + this.name = name; + this.argumentTypes = argumentTypes; + } + } + + private final Map classSuperClass = new HashMap<>(); + private final Map classInterfaces = new HashMap<>(); + private final Map> classMethods = new HashMap<>(); + + private String className; + + public Double getAvgMethodOverrides() { + Map overridenMethodCounts = getOverridenMethodCounts(); + Integer count = overridenMethodCounts.size(); + Integer sum = overridenMethodCounts.values().stream().reduce(Integer::sum).orElse(count); + return Double.valueOf(sum) / count; + } + + private Map getOverridenMethodCounts() { + Map result = new HashMap<>(); + + for (String className : classMethods.keySet()) { + Integer inheritedOverridenCount = inheritedOverridenMethodCount(className, classSuperClass.get(className), + 0); + Integer implementedMethodCount = implementedMethodCount(className); + + result.put(className, inheritedOverridenCount + implementedMethodCount); + } + + classMethods.values().stream() + .flatMap(list -> list.stream()) + .forEach(method -> method.overrideFound = false); + + return result; + } + + private Integer implementedMethodCount(String className) { + String[] interfaces = classInterfaces.get(className); + if (interfaces == null || interfaces.length == 0) { + return 0; + } + + Integer result = 0; + for (Method method : classMethods.get(className)) { + if (method.overrideFound) { + continue; + } + + for (String interfaceName : interfaces) { + List interfaceMethods = classMethods.get(interfaceName); + if (interfaceMethods == null) { + continue; + } + + for (Method interfaceMethod : interfaceMethods) { + if (method.equals(interfaceMethod)) { + result++; + method.overrideFound = true; + } + } + } + } + + return result; + } + + private Integer inheritedOverridenMethodCount(String className, String superName, Integer result) { + if (superName == null) { + return result; + } + + List superMethods = classMethods.get(superName); + if (superMethods == null) { + return result; + } + + for (Method method : classMethods.get(className)) { + if (method.overrideFound) { + continue; + } + + for (Method superMethod : superMethods) { + if (method.equals(superMethod)) { + result++; + method.overrideFound = true; + } + } + } + + return inheritedOverridenMethodCount(className, classSuperClass.get(superName), result); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + className = name; + + classMethods.put(name, new ArrayList<>()); + + if ((access & ACC_INTERFACE) == ACC_INTERFACE) { + return; + } + + classInterfaces.put(className, interfaces); + classSuperClass.put(className, superName); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if ("".equals(name) || "".equals(name)) { + return null; + } + + if ((access & ACC_PRIVATE) == ACC_PRIVATE) { + return null; + } + + var method = new Method(name, Arrays.toString(Type.getArgumentTypes(desc))); + classMethods.get(className).add(method); + + return null; + } +} \ No newline at end of file From e09dc2d701bced16e5f15b4b1d857b2b3d0c7abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=94=D0=B0=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=BE=D0=B2?= Date: Thu, 4 Dec 2025 17:50:08 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/analyzer/visitor/OverrideVisitor.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/example/analyzer/visitor/OverrideVisitor.java b/src/main/java/org/example/analyzer/visitor/OverrideVisitor.java index bf8a128..b1f54de 100644 --- a/src/main/java/org/example/analyzer/visitor/OverrideVisitor.java +++ b/src/main/java/org/example/analyzer/visitor/OverrideVisitor.java @@ -20,12 +20,12 @@ public OverrideVisitor() { static class Method { final String name; - final String argumentTypes; + final String params; boolean overrideFound = false; - Method(String name, String argumentTypes) { + Method(String name, String params) { this.name = name; - this.argumentTypes = argumentTypes; + this.params = params; } } @@ -79,7 +79,8 @@ private Integer implementedMethodCount(String className) { } for (Method interfaceMethod : interfaceMethods) { - if (method.equals(interfaceMethod)) { + if (method.name.equals(interfaceMethod.name) + && method.params.equals(interfaceMethod.params)) { result++; method.overrideFound = true; } @@ -106,7 +107,8 @@ private Integer inheritedOverridenMethodCount(String className, String superName } for (Method superMethod : superMethods) { - if (method.equals(superMethod)) { + if (method.name.equals(superMethod.name) + && method.params.equals(superMethod.params)) { result++; method.overrideFound = true; } From 2a6520125ce8730da28ec1e6bd7bc87cf9b48a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=94=D0=B0=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=BE=D0=B2?= Date: Thu, 4 Dec 2025 17:52:08 +0300 Subject: [PATCH 6/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B4=D0=B5=D1=84=D0=BE=D0=BB=D1=82=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=84=D0=B0=D0=B9=D0=BB=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/example/App.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/App.java b/src/main/java/org/example/App.java index 0279634..4cefa3f 100644 --- a/src/main/java/org/example/App.java +++ b/src/main/java/org/example/App.java @@ -11,7 +11,7 @@ public class App { private static final String defaultJar = "src/main/resources/sample.jar"; - private static final String defaultOutput = null; + private static final String defaultOutput = "./output.json"; public static void main(String[] args) throws IOException { String jar = defaultJar; From 03996282428d2679c39792f0e54b68c3f7a3939c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=94=D0=B0=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=BE=D0=B2?= Date: Thu, 4 Dec 2025 18:39:01 +0300 Subject: [PATCH 7/8] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83=20=D0=BD=D0=B0=20@Ov?= =?UTF-8?q?erride?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/example/analyzer/Analyzer.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/org/example/analyzer/Analyzer.java b/src/main/java/org/example/analyzer/Analyzer.java index fe466f9..945a653 100644 --- a/src/main/java/org/example/analyzer/Analyzer.java +++ b/src/main/java/org/example/analyzer/Analyzer.java @@ -52,16 +52,6 @@ private void processClass(ClassReader reader) { classMetrics.assignments = abcVisitor.getAssignments(); classMetrics.branches = abcVisitor.getBranches(); classMetrics.conditionals = abcVisitor.getConditionals(); - - classMetrics.overrides = (int) node.fields.stream().filter((fieldNode) -> { - if (fieldNode.visibleAnnotations == null) { - return false; - } - - return fieldNode.visibleAnnotations.stream() - .anyMatch((annotationNode) -> annotationNode.desc.equals("Ljava/lang/Override;")); - }).count(); - classMetrics.fields = node.fields.size(); classMetircsMap.put(node.name, classMetrics); From 6635be7c87f19e2ce4088159e56b407a37d26623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=94=D0=B0=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=BE=D0=B2?= Date: Thu, 4 Dec 2025 18:44:44 +0300 Subject: [PATCH 8/8] =?UTF-8?q?=D0=9F=D0=BE=D1=87=D0=B8=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=20NPE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/example/analyzer/Analyzer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/example/analyzer/Analyzer.java b/src/main/java/org/example/analyzer/Analyzer.java index 945a653..4744df0 100644 --- a/src/main/java/org/example/analyzer/Analyzer.java +++ b/src/main/java/org/example/analyzer/Analyzer.java @@ -62,11 +62,9 @@ private JarMetrics calcMetrics() { Long totalBranches = Long.valueOf(0); Long totalConditions = Long.valueOf(0); - Integer totalOverrides = 0; Integer totalFields = 0; for (ClassMetrics classMetrics : classMetircsMap.values()) { - totalOverrides += classMetrics.overrides; totalFields += classMetrics.fields; totalAssigments += classMetrics.assignments; totalBranches += classMetrics.branches;