Skip to content

Commit

Permalink
671-add-support-for-exceptions-caught-be-a-method-to-java-plugin (#768)
Browse files Browse the repository at this point in the history
* #671 added visitor to scan caught exceptions

* #671 label thrown and caught types with "Throwable"
  • Loading branch information
DirkMahler authored Jan 13, 2025
1 parent 0946ef6 commit 38d1f65
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 49 deletions.
14 changes: 13 additions & 1 deletion plugin/java/src/main/asciidoc/scanner.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ Qualifies a Java type as record.
=== Nodes labeled with `:Java:Type:Annotation`
Qualifies a Java type as annotation.

[[:Java:Type:Throwable]]
=== Nodes labeled with :Java:Type:Throwable
A throwable Java type.

[[:Java:Field]]
=== Nodes labeled with `:Java:Field`
A field declared in a Java type.
Expand Down Expand Up @@ -225,7 +229,8 @@ A method declared in a Java type.
| Name | Target label(s) | Cardinality | Description
| DECLARES_TYPE_PARAMETER | <<:Java:Bound:TypeVariable>> | 0..n | Declares a type variable
| HAS | <<:Java:Parameter>> | 0..n | References a parameter of the method
| THROWS | <<:Java:Type>> | 0..n | References the exception types thrown by the method
| CATCHES | <<:Java:Type:Throwable>> | 0..n | References the exception types caught by a method
| THROWS | <<:Java:Type:Throwable>> | 0..n | References the exception types thrown by the method
| THROWS_GENERIC | <<:Java:Bound>> | 0..n | References the generic exception types thrown by the method
| RETURNS | <<:Java:Type>> | 0..n | References the return type of the method
| RETURNS_GENERIC | <<:Java:Bound>> | 0..n | References the generic return type of the method
Expand Down Expand Up @@ -254,6 +259,13 @@ scanned themselves will only provide the property `signature`
| lineNumber | The line number where the operation is performed (not available if the scanned bytecode is compiled without line number information)
|====

.Properties of `:CATCHES`
[options="header"]
|====
| Name | Description
| firstLineNumber | The first line number (inclusive) where the referenced exception is caught
| lastLineNumber | The last line number (exclusive) where the referenced exception is caught
|====

.Properties of `:DECLARES_TYPE_PARAMETER`
[options="header"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.buschmais.jqassistant.plugin.java.api.model;

import com.buschmais.jqassistant.core.store.api.model.Descriptor;
import com.buschmais.xo.neo4j.api.annotation.Relation;
import com.buschmais.xo.neo4j.api.annotation.Relation.Incoming;
import com.buschmais.xo.neo4j.api.annotation.Relation.Outgoing;

@Relation("CATCHES")
public interface CatchesDescriptor extends Descriptor {

@Outgoing
MethodDescriptor getMethod();

@Incoming
TypeDescriptor getExceptionType();

Integer getFirstLineNumber();

void setFirstLineNumber(Integer firstLineNumber);

Integer getLastLineNumber();

void setLastLineNumber(Integer lastLineNumber);
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ public interface MethodDescriptor extends MemberDescriptor, AbstractDescriptor {
*/
List<ThrowsDescriptor> getThrows();

/**
* Return all caught exception types.
*
* @return The caught exception types.
*/
@Relation("CATCHES")
List<CatchesDescriptor> getCatches();

/**
* Return <code>true</code> if this method is native.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.buschmais.jqassistant.plugin.java.api.model;

import com.buschmais.xo.neo4j.api.annotation.Label;

/**
* Represents a {@link Throwable}.
*/
@Label("Throwable")
public interface ThrowableDescriptor extends TypeDescriptor {
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,6 @@ public interface TypeDescriptor extends JavaByteCodeDescriptor, PackageMemberDes

List<ThrowsDescriptor> getThrownBy();

List<CatchesDescriptor> getCatchingMethods();

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ public class ClassVisitor extends org.objectweb.asm.ClassVisitor {
* Constructor.
*
* @param fileDescriptor
* The file descriptor to be migrated to a type descriptor.
* The file descriptor to be migrated to a type descriptor.
* @param visitorHelper
* The visitor helper.
* The visitor helper.
*/
public ClassVisitor(FileDescriptor fileDescriptor, VisitorHelper visitorHelper) {
super(VisitorHelper.ASM_OPCODES);
Expand Down Expand Up @@ -79,7 +79,7 @@ public void visit(final int version, final int access, final String name, final
String fullQualifiedName = SignatureHelper.getObjectType(name);
cachedType = visitorHelper.createType(fullQualifiedName, fileDescriptor, (Class<? extends ClassFileDescriptor>) javaByteCodeType);
visitorHelper.getTypeVariableResolver()
.push();
.push();
ClassFileDescriptor classFileDescriptor = cachedType.getTypeDescriptor();
this.javaByteCodeDescriptor = classFileDescriptor;
if (visitorHelper.hasFlag(access, Opcodes.ACC_ABSTRACT) && !visitorHelper.hasFlag(access, Opcodes.ACC_INTERFACE)) {
Expand All @@ -88,13 +88,13 @@ public void visit(final int version, final int access, final String name, final
setModifiers(access, classFileDescriptor);
if (signature == null) {
TypeDescriptor superClassType = visitorHelper.resolveType(SignatureHelper.getObjectType(superName), cachedType)
.getTypeDescriptor();
.getTypeDescriptor();
classFileDescriptor.setSuperClass(superClassType);
for (int i = 0; i < interfaces.length; i++) {
TypeDescriptor interfaceType = visitorHelper.resolveType(SignatureHelper.getObjectType(interfaces[i]), cachedType)
.getTypeDescriptor();
.getTypeDescriptor();
classFileDescriptor.getInterfaces()
.add(interfaceType);
.add(interfaceType);
}
} else {
new SignatureReader(signature).accept(new ClassSignatureVisitor(cachedType, visitorHelper));
Expand All @@ -106,7 +106,7 @@ public void visit(final int version, final int access, final String name, final
public ModuleVisitor visitModule(String name, int access, String version) {
ScannerContext scannerContext = visitorHelper.getScannerContext();
ModuleDescriptor moduleDescriptor = scannerContext.getStore()
.addDescriptorType(fileDescriptor, ModuleDescriptor.class);
.addDescriptorType(fileDescriptor, ModuleDescriptor.class);
moduleDescriptor.setFullQualifiedName(name);
moduleDescriptor.setVersion(version);
moduleDescriptor.setOpen(visitorHelper.hasFlag(access, Opcodes.ACC_OPEN));
Expand All @@ -118,9 +118,9 @@ public ModuleVisitor visitModule(String name, int access, String version) {
@Override
public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {
cachedType.getTypeDescriptor()
.setStatic(true);
.setStatic(true);
cachedType.getTypeDescriptor()
.setFinal(true);
.setFinal(true);
return null;
}

Expand All @@ -133,7 +133,7 @@ public FieldVisitor visitField(final int access, final String name, final String
setModifiers(access, fieldDescriptor);
if (signature == null) {
TypeDescriptor typeDescriptor = visitorHelper.resolveType(SignatureHelper.getType((desc)), cachedType)
.getTypeDescriptor();
.getTypeDescriptor();
fieldDescriptor.setType(typeDescriptor);
} else {
new SignatureReader(signature).accept(new AbstractBoundVisitor(visitorHelper, cachedType) {
Expand All @@ -157,15 +157,15 @@ protected void apply(TypeDescriptor rawTypeBound, BoundDescriptor bound) {

@Override
public org.objectweb.asm.MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature,
final String[] exceptions) {
final String[] exceptions) {
String methodSignature = SignatureHelper.getMethodSignature(name, desc);
MethodDescriptor methodDescriptor = visitorHelper.getMethodDescriptor(cachedType, methodSignature);
if (isLambda(name, access)) {
visitorHelper.getStore()
.addDescriptorType(methodDescriptor, LambdaMethodDescriptor.class);
.addDescriptorType(methodDescriptor, LambdaMethodDescriptor.class);
}
visitorHelper.getTypeVariableResolver()
.push();
.push();
methodDescriptor.setName(name);
setModifiers(access, methodDescriptor);
if (visitorHelper.hasFlag(access, Opcodes.ACC_ABSTRACT)) {
Expand All @@ -177,12 +177,12 @@ public org.objectweb.asm.MethodVisitor visitMethod(final int access, final Strin
if (signature == null) {
String returnType = SignatureHelper.getType(org.objectweb.asm.Type.getReturnType(desc));
methodDescriptor.setReturns(visitorHelper.resolveType(returnType, cachedType)
.getTypeDescriptor());
.getTypeDescriptor());
org.objectweb.asm.Type[] types = org.objectweb.asm.Type.getArgumentTypes(desc);
for (int i = 0; i < types.length; i++) {
String parameterType = SignatureHelper.getType(types[i]);
TypeDescriptor typeDescriptor = visitorHelper.resolveType(parameterType, cachedType)
.getTypeDescriptor();
.getTypeDescriptor();
ParameterDescriptor parameterDescriptor = visitorHelper.addParameterDescriptor(methodDescriptor, i);
parameterDescriptor.setType(typeDescriptor);
}
Expand All @@ -191,32 +191,34 @@ public org.objectweb.asm.MethodVisitor visitMethod(final int access, final Strin
}
for (int i = 0; exceptions != null && i < exceptions.length; i++) {
TypeDescriptor exceptionType = visitorHelper.resolveType(SignatureHelper.getObjectType(exceptions[i]), cachedType)
.getTypeDescriptor();
.getTypeDescriptor();
visitorHelper.getStore()
.addDescriptorType(exceptionType, ThrowableDescriptor.class);
ThrowsDescriptor throwsDescriptor = visitorHelper.getStore()
.create(methodDescriptor, ThrowsDescriptor.class, exceptionType);
.create(methodDescriptor, ThrowsDescriptor.class, exceptionType);
throwsDescriptor.setDeclaration(true);
}
Type type = getObjectType(typeName);
MethodNode methodNode = new MethodNode(access, name, desc, signature, exceptions);
MethodDataFlowVerifier methodDataFlowVerifier = getMethodDataFlowVerifier(type);
return new DelegatingMethodVisitor(new MethodVisitor(cachedType, methodDescriptor, visitorHelper), new MethodLoCVisitor(methodDescriptor),
new MethodComplexityVisitor(methodDescriptor),
new MethodDataFlowVisitor(type, methodDescriptor, methodNode, methodDataFlowVerifier, visitorHelper));
return new DelegatingMethodVisitor(new MethodVisitor(cachedType, methodDescriptor, visitorHelper),
new MethodCatchesVisitor(methodDescriptor, visitorHelper), new MethodLoCVisitor(methodDescriptor), new MethodComplexityVisitor(methodDescriptor),
new MethodDataFlowVisitor(type, methodDescriptor, methodNode, methodDataFlowVerifier, visitorHelper));
}

private MethodDataFlowVerifier getMethodDataFlowVerifier(Type type) {
return new MethodDataFlowVerifier(type, InterfaceTypeDescriptor.class.isAssignableFrom(this.cachedType.getTypeDescriptor()
.getClass()), getObjectType(superTypeName), stream(interfaceNames).map(Type::getObjectType)
.collect(toList()));
.getClass()), getObjectType(superTypeName), stream(interfaceNames).map(Type::getObjectType)
.collect(toList()));
}

/**
* Determine if a method represents a lambda expression.
*
* @param name
* The method name.
* The method name.
* @param access
* The access modifiers.
* The access modifiers.
* @return <code>true</code> if the method represents a lambda expression.
*/
private boolean isLambda(String name, int access) {
Expand Down Expand Up @@ -248,18 +250,18 @@ public void visitSource(final String source, final String debug) {
@Override
public void visitInnerClass(final String name, final String outerName, final String innerName, final int access) {
String fullQualifiedName = cachedType.getTypeDescriptor()
.getFullQualifiedName();
.getFullQualifiedName();
// innerName always represents the name of the inner class
String innerTypeName = SignatureHelper.getObjectType(name);
TypeDescriptor innerType = visitorHelper.resolveType(innerTypeName, cachedType)
.getTypeDescriptor();
.getTypeDescriptor();
// set relation only if outerName is current class
if (outerName != null) {
String outerTypeName = SignatureHelper.getObjectType(outerName);
if (fullQualifiedName.equals(outerTypeName)) {
cachedType.getTypeDescriptor()
.getDeclaredInnerClasses()
.add(innerType);
.getDeclaredInnerClasses()
.add(innerType);
}
}
}
Expand All @@ -270,13 +272,13 @@ public void visitOuterClass(final String owner, final String name, final String
TypeCache.CachedType<?> cachedOuterType = visitorHelper.resolveType(outerTypeName, this.cachedType);
TypeDescriptor innerType = this.cachedType.getTypeDescriptor();
cachedOuterType.getTypeDescriptor()
.getDeclaredInnerClasses()
.add(innerType);
.getDeclaredInnerClasses()
.add(innerType);
if (name != null) {
String methodSignature = SignatureHelper.getMethodSignature(name, desc);
MethodDescriptor methodDescriptor = visitorHelper.getMethodDescriptor(cachedOuterType, methodSignature);
methodDescriptor.getDeclaredInnerClasses()
.add(innerType);
.add(innerType);
}
}

Expand All @@ -294,15 +296,15 @@ public void visitEnd() {
if (cachedType != null) {
visitorHelper.storeDependencies(cachedType);
visitorHelper.getTypeVariableResolver()
.pop();
.pop();
}
}

/**
* Returns the AccessModifier for the flag pattern.
*
* @param flags
* the flags
* the flags
* @return the AccessModifier
*/
private VisibilityModifier getVisibility(int flags) {
Expand All @@ -325,7 +327,7 @@ private VisibilityModifier getVisibility(int flags) {
* Determine the types label to be applied to a class node.
*
* @param flags
* The access flags.
* The access flags.
* @return The types label.
*/
private Class<? extends JavaByteCodeDescriptor> getJavaByteCodeType(int flags) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.buschmais.jqassistant.plugin.java.impl.scanner.visitor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.buschmais.jqassistant.plugin.java.api.model.CatchesDescriptor;
import com.buschmais.jqassistant.plugin.java.api.model.MethodDescriptor;
import com.buschmais.jqassistant.plugin.java.api.model.ThrowableDescriptor;
import com.buschmais.jqassistant.plugin.java.api.model.TypeDescriptor;
import com.buschmais.jqassistant.plugin.java.api.scanner.SignatureHelper;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

/**
* Visitor for methods catching exceptions.
*/
class MethodCatchesVisitor extends MethodVisitor {

private final MethodDescriptor methodDescriptor;

private final VisitorHelper visitorHelper;

private List<TryCatchBlock> tryCatchBlocks = new ArrayList<>();

private Map<Label, Integer> lineNumbers = new HashMap<>();

public MethodCatchesVisitor(MethodDescriptor methodDescriptor, VisitorHelper visitorHelper) {
super(VisitorHelper.ASM_OPCODES);
this.methodDescriptor = methodDescriptor;
this.visitorHelper = visitorHelper;
}

@Override
public void visitLineNumber(int line, Label start) {
this.lineNumbers.put(start, line);
}

@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
if (type != null) {
tryCatchBlocks.add(new TryCatchBlock(type, start, end, handler));
}
}

@Override
public void visitEnd() {
for (TryCatchBlock tryCatchBlock : tryCatchBlocks) {
String throwableType = SignatureHelper.getObjectType(tryCatchBlock.getType());
TypeDescriptor typeDescriptor = visitorHelper.resolveType(throwableType)
.getTypeDescriptor();
visitorHelper.getStore()
.addDescriptorType(typeDescriptor, ThrowableDescriptor.class);
CatchesDescriptor catchesDescriptor = visitorHelper.getStore()
.create(methodDescriptor, CatchesDescriptor.class, typeDescriptor);
catchesDescriptor.setFirstLineNumber(lineNumbers.get(tryCatchBlock.getStart()));
catchesDescriptor.setLastLineNumber(lineNumbers.get(tryCatchBlock.getEnd()));
}
}

/**
* Represents a try-block
*/
@Getter
@RequiredArgsConstructor
@ToString
private static final class TryCatchBlock {

private final String type;

private final Label start;

private final Label end;

private final Label handler;
}
}

Loading

0 comments on commit 38d1f65

Please sign in to comment.