From e0e0c65ef4d60ad10005feb1bee1d2e5bfb11a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Siersze=C5=84?= Date: Fri, 25 Feb 2022 13:04:16 +0100 Subject: [PATCH] Add support to test caught exceptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will add `TryCatchBlocks` to the ArchUnit core and introduce `JavaCodeUnit.getTryCatchBlocks()` to examine try-catch blocks that have been parsed from the bytecode of a method or constructor. We also add an extension `JavaAccess.getContainingTryBlocks()` to make it easy to verify that certain accesses in code are wrapped into certain try-catch blocks (e.g. "whenever method x is called there should be a try-catch block to handle exception case y"). Signed-off-by: Krzysztof Sierszeń Signed-off-by: Peter Gafert --- .../domain/DomainObjectCreationContext.java | 5 + .../archunit/core/domain/ImportContext.java | 13 +- .../archunit/core/domain/JavaAccess.java | 12 + .../archunit/core/domain/JavaCodeUnit.java | 22 +- .../archunit/core/domain/TryCatchBlock.java | 75 +++++ .../archunit/core/importer/AccessRecord.java | 7 + .../core/importer/ClassFileImportRecord.java | 11 + .../core/importer/ClassFileProcessor.java | 55 +++- .../core/importer/ClassGraphCreator.java | 43 ++- .../core/importer/DomainBuilders.java | 64 +++++ .../core/importer/JavaClassProcessor.java | 50 +++- .../core/importer/RawAccessRecord.java | 22 +- .../core/importer/TryCatchRecorder.java | 179 ++++++++++++ .../archunit/core/domain/TestUtils.java | 6 +- .../ClassFileImporterAccessesTest.java | 260 ++++++++++++++++++ .../core/importer/ImportTestUtils.java | 16 +- .../trycatch/ClassHoldingMethods.java | 21 ++ .../ClassWithComplexTryCatchBlocks.java | 30 ++ .../ClassWithSimpleTryCatchBlocks.java | 23 ++ ...assWithTryCatchBlockWithoutThrowables.java | 12 + .../trycatch/ClassWithTryWithResources.java | 19 ++ .../tngtech/archunit/testutil/Assertions.java | 11 + .../archunit/testutil/JavaCallQuery.java | 46 ++++ .../assertion/CodeUnitAccessAssertion.java | 26 ++ .../assertion/TryCatchBlockAssertion.java | 24 ++ .../assertion/TryCatchBlocksAssertion.java | 76 +++++ 26 files changed, 1078 insertions(+), 50 deletions(-) create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/domain/TryCatchBlock.java create mode 100644 archunit/src/main/java/com/tngtech/archunit/core/importer/TryCatchRecorder.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassHoldingMethods.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithComplexTryCatchBlocks.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithSimpleTryCatchBlocks.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithTryCatchBlockWithoutThrowables.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/trycatch/ClassWithTryWithResources.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/JavaCallQuery.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/TryCatchBlockAssertion.java create mode 100644 archunit/src/test/java/com/tngtech/archunit/testutil/assertion/TryCatchBlocksAssertion.java diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 6ef359216b..11cf906c86 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -44,6 +44,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodReferenceBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaWildcardTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; import static com.google.common.base.Preconditions.checkArgument; @@ -107,6 +108,10 @@ public static JavaField createJavaField(JavaFieldBuilder builder) { return new JavaField(builder); } + public static TryCatchBlock createTryCatchBlock(TryCatchBlockBuilder builder) { + return new TryCatchBlock(builder); + } + public static JavaFieldAccess createJavaFieldAccess(JavaFieldAccessBuilder builder) { return new JavaFieldAccess(builder); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java index 5973f162ec..5b4aa1c14b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java @@ -21,6 +21,7 @@ import java.util.Set; import com.tngtech.archunit.Internal; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; @Internal public interface ImportContext { @@ -50,15 +51,17 @@ public interface ImportContext { Optional createEnclosingCodeUnit(JavaClass owner); - Set createFieldAccessesFor(JavaCodeUnit codeUnit); + Set createFieldAccessesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); - Set createMethodCallsFor(JavaCodeUnit codeUnit); + Set createMethodCallsFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); - Set createConstructorCallsFor(JavaCodeUnit codeUnit); + Set createConstructorCallsFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); - Set createMethodReferencesFor(JavaCodeUnit codeUnit); + Set createMethodReferencesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); - Set createConstructorReferencesFor(JavaCodeUnit codeUnit); + Set createConstructorReferencesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders); + + Set createTryCatchBlockBuilders(JavaCodeUnit codeUnit); JavaClass resolveClass(String fullyQualifiedClassName); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java index 08e3313168..6bc7c0e3c2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAccess.java @@ -16,6 +16,7 @@ package com.tngtech.archunit.core.domain; import java.util.Objects; +import java.util.Set; import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ChainableFunction; @@ -28,6 +29,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; public abstract class JavaAccess @@ -128,6 +130,16 @@ public String getDescription() { protected abstract String descriptionVerb(); + /** + * @return All try-catch-blocks where this {@link JavaAccess} is contained within the try-part the try-catch-block + */ + @PublicAPI(usage = ACCESS) + public Set getContainingTryBlocks() { + return getOrigin().getTryCatchBlocks().stream() + .filter(block -> block.getAccessesContainedInTryBlock().contains(this)) + .collect(toImmutableSet()); + } + public static final class Predicates { private Predicates() { } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java index 5fbde5d45b..4c3276db18 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java @@ -34,7 +34,9 @@ import com.tngtech.archunit.core.domain.properties.HasThrowsClause; import com.tngtech.archunit.core.domain.properties.HasTypeParameters; import com.tngtech.archunit.core.importer.DomainBuilders.JavaCodeUnitBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Sets.union; import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.core.domain.Formatters.formatMethod; @@ -66,6 +68,7 @@ public abstract class JavaCodeUnit private Set constructorCalls = Collections.emptySet(); private Set methodReferences = Collections.emptySet(); private Set constructorReferences = Collections.emptySet(); + private Set tryCatchBlocks = Collections.emptySet(); JavaCodeUnit(JavaCodeUnitBuilder builder) { super(builder); @@ -203,6 +206,11 @@ public Set getInstanceofChecks() { return instanceofChecks; } + @PublicAPI(usage = ACCESS) + public Set getTryCatchBlocks() { + return tryCatchBlocks; + } + @PublicAPI(usage = ACCESS) public Set> getCallsFromSelf() { return union(getMethodCallsFromSelf(), getConstructorCallsFromSelf()); @@ -261,11 +269,15 @@ public List>> getParameterAnnotations() { } void completeAccessesFrom(ImportContext context) { - fieldAccesses = context.createFieldAccessesFor(this); - methodCalls = context.createMethodCallsFor(this); - constructorCalls = context.createConstructorCallsFor(this); - methodReferences = context.createMethodReferencesFor(this); - constructorReferences = context.createConstructorReferencesFor(this); + Set tryCatchBlockBuilders = context.createTryCatchBlockBuilders(this); + fieldAccesses = context.createFieldAccessesFor(this, tryCatchBlockBuilders); + methodCalls = context.createMethodCallsFor(this, tryCatchBlockBuilders); + constructorCalls = context.createConstructorCallsFor(this, tryCatchBlockBuilders); + methodReferences = context.createMethodReferencesFor(this, tryCatchBlockBuilders); + constructorReferences = context.createConstructorReferencesFor(this, tryCatchBlockBuilders); + tryCatchBlocks = tryCatchBlockBuilders.stream() + .map(builder -> builder.build(this, context)) + .collect(toImmutableSet()); } @ResolvesTypesViaReflection diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/TryCatchBlock.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/TryCatchBlock.java new file mode 100644 index 0000000000..39c833e7ef --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/TryCatchBlock.java @@ -0,0 +1,75 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.domain; + +import java.util.Set; + +import com.google.common.collect.ImmutableSet; +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.core.domain.properties.HasOwner; +import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; +import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf; + +@PublicAPI(usage = ACCESS) +public final class TryCatchBlock implements HasOwner, HasSourceCodeLocation { + private final JavaCodeUnit owner; + private final Set caughtThrowables; + private final SourceCodeLocation sourceCodeLocation; + private final Set> accessesContainedInTryBlock; + + TryCatchBlock(TryCatchBlockBuilder builder) { + this.owner = checkNotNull(builder.getOwner()); + this.caughtThrowables = ImmutableSet.copyOf(builder.getCaughtThrowables()); + this.sourceCodeLocation = checkNotNull(builder.getSourceCodeLocation()); + this.accessesContainedInTryBlock = ImmutableSet.copyOf(builder.getAccessesContainedInTryBlock()); + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaCodeUnit getOwner() { + return owner; + } + + @PublicAPI(usage = ACCESS) + public Set getCaughtThrowables() { + return caughtThrowables; + } + + @Override + @PublicAPI(usage = ACCESS) + public SourceCodeLocation getSourceCodeLocation() { + return sourceCodeLocation; + } + + @PublicAPI(usage = ACCESS) + public Set> getAccessesContainedInTryBlock() { + return accessesContainedInTryBlock; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("owner", owner.getFullName()) + .add("caughtThrowables", namesOf(caughtThrowables)) + .add("location", sourceCodeLocation) + .toString(); + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java index ecd9c0fc19..385c815eb3 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java @@ -54,6 +54,8 @@ interface AccessRecord { int getLineNumber(); + RawAccessRecord getRaw(); + @Internal interface FieldAccessRecord extends AccessRecord { AccessType getAccessType(); @@ -260,6 +262,11 @@ public TARGET getTarget() { public int getLineNumber() { return record.lineNumber; } + + @Override + public RawAccessRecord getRaw() { + return record; + } } private static class RawFieldAccessRecordProcessed extends RawAccessRecordProcessed implements FieldAccessRecord { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java index 1ae9465a12..385ed40076 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java @@ -31,6 +31,7 @@ import com.google.common.collect.ListMultimap; import com.google.common.collect.SetMultimap; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaCodeUnit; import com.tngtech.archunit.core.domain.JavaMember; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder; @@ -43,6 +44,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaTypeParameterBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; import static com.google.common.base.Preconditions.checkArgument; @@ -66,6 +68,7 @@ class ClassFileImportRecord { private final SetMultimap annotationsByOwner = HashMultimap.create(); private final Map annotationDefaultValuesByOwner = new HashMap<>(); private final EnclosingDeclarationsByInnerClasses enclosingDeclarationsByOwner = new EnclosingDeclarationsByInnerClasses(); + private final SetMultimap tryCatchBlocksByOwner = HashMultimap.create(); private final Set rawFieldAccessRecords = new HashSet<>(); private final Set rawMethodCallRecords = new HashSet<>(); @@ -135,6 +138,10 @@ void setEnclosingCodeUnit(String ownerName, CodeUnit enclosingCodeUnit) { enclosingDeclarationsByOwner.registerEnclosingCodeUnit(ownerName, enclosingCodeUnit); } + void addTryCatchBlocks(String declaringClassName, String methodName, String descriptor, Set tryCatchBlocks) { + tryCatchBlocksByOwner.putAll(getMemberKey(declaringClassName, methodName, descriptor), tryCatchBlocks); + } + Optional getSuperclassFor(String name) { return Optional.ofNullable(superclassNamesByOwner.get(name)); } @@ -238,6 +245,10 @@ Optional getEnclosingCodeUnitFor(String ownerName) { return enclosingDeclarationsByOwner.getEnclosingCodeUnit(ownerName); } + Set getTryCatchBlockBuildersFor(JavaCodeUnit codeUnit) { + return tryCatchBlocksByOwner.get(getMemberKey(codeUnit)); + } + void registerFieldAccess(RawAccessRecord.ForField record) { rawFieldAccessRecords.add(record); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java index 4f24ec17eb..0c1a9d3d5b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java @@ -24,6 +24,7 @@ import com.tngtech.archunit.ArchConfiguration; import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder; @@ -33,12 +34,15 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; import com.tngtech.archunit.core.importer.JavaClassProcessor.AccessHandler; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; import com.tngtech.archunit.core.importer.RawAccessRecord.TargetInfo; +import com.tngtech.archunit.core.importer.TryCatchRecorder.TryCatchBlocksFinishedListener; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; import com.tngtech.archunit.core.importer.resolvers.ClassResolver.ClassUriImporter; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Label; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -196,13 +200,14 @@ public void onDeclaredGenericSignatureType(String typeName) { } } - private static class RecordAccessHandler implements AccessHandler { + private static class RecordAccessHandler implements AccessHandler, TryCatchBlocksFinishedListener { private static final Logger LOG = LoggerFactory.getLogger(RecordAccessHandler.class); private final ClassFileImportRecord importRecord; private final DependencyResolutionProcess dependencyResolutionProcess; private CodeUnit codeUnit; private int lineNumber; + private final TryCatchRecorder tryCatchRecorder = new TryCatchRecorder(this); private RecordAccessHandler(ClassFileImportRecord importRecord, DependencyResolutionProcess dependencyResolutionProcess) { this.importRecord = importRecord; @@ -215,8 +220,14 @@ public void setContext(CodeUnit codeUnit) { } @Override - public void setLineNumber(int lineNumber) { + public void onLineNumber(int lineNumber, Label label) { this.lineNumber = lineNumber; + tryCatchRecorder.onEncounteredLabel(label, lineNumber); + } + + @Override + public void onLabel(Label label) { + tryCatchRecorder.onEncounteredLabel(label); } @Override @@ -224,9 +235,11 @@ public void handleFieldInstruction(int opcode, String owner, String name, String AccessType accessType = AccessType.forOpCode(opcode); LOG.trace("Found {} access to field {}.{}:{} in line {}", accessType, owner, name, desc, lineNumber); TargetInfo target = new TargetInfo(owner, name, desc); - importRecord.registerFieldAccess(filled(new RawAccessRecord.ForField.Builder(), target) + RawAccessRecord.ForField accessRecord = filled(new RawAccessRecord.ForField.Builder(), target) .withAccessType(accessType) - .build()); + .build(); + importRecord.registerFieldAccess(accessRecord); + tryCatchRecorder.registerAccess(accessRecord); dependencyResolutionProcess.registerAccessToType(target.owner.getFullyQualifiedClassName()); } @@ -234,11 +247,13 @@ public void handleFieldInstruction(int opcode, String owner, String name, String public void handleMethodInstruction(String owner, String name, String desc) { LOG.trace("Found call of method {}.{}:{} in line {}", owner, name, desc, lineNumber); TargetInfo target = new TargetInfo(owner, name, desc); + RawAccessRecord accessRecord = filled(new RawAccessRecord.Builder(), target).build(); if (CONSTRUCTOR_NAME.equals(name)) { - importRecord.registerConstructorCall(filled(new RawAccessRecord.Builder(), target).build()); + importRecord.registerConstructorCall(accessRecord); } else { - importRecord.registerMethodCall(filled(new RawAccessRecord.Builder(), target).build()); + importRecord.registerMethodCall(accessRecord); } + tryCatchRecorder.registerAccess(accessRecord); dependencyResolutionProcess.registerAccessToType(target.owner.getFullyQualifiedClassName()); } @@ -246,14 +261,38 @@ public void handleMethodInstruction(String owner, String name, String desc) { public void handleMethodReferenceInstruction(String owner, String name, String desc) { LOG.trace("Found method reference {}.{}:{} in line {}", owner, name, desc, lineNumber); TargetInfo target = new TargetInfo(owner, name, desc); + RawAccessRecord accessRecord = filled(new RawAccessRecord.Builder(), target).build(); if (CONSTRUCTOR_NAME.equals(name)) { - importRecord.registerConstructorReference(filled(new RawAccessRecord.Builder(), target).build()); + importRecord.registerConstructorReference(accessRecord); } else { - importRecord.registerMethodReference(filled(new RawAccessRecord.Builder(), target).build()); + importRecord.registerMethodReference(accessRecord); } + tryCatchRecorder.registerAccess(accessRecord); dependencyResolutionProcess.registerAccessToType(target.owner.getFullyQualifiedClassName()); } + @Override + public void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType) { + LOG.trace("Found try/catch block between {} and {} for throwable {}", start, end, throwableType); + tryCatchRecorder.registerTryCatchBlock(start, end, handler, throwableType); + } + + @Override + public void handleTryFinallyBlock(Label start, Label end, Label handler) { + LOG.trace("Found try/finally block between {} and {}", start, end); + tryCatchRecorder.registerTryFinallyBlock(start, end, handler); + } + + @Override + public void onMethodEnd() { + tryCatchRecorder.onEncounteredMethodEnd(); + } + + @Override + public void onTryCatchBlocksFinished(Set tryCatchBlocks) { + importRecord.addTryCatchBlocks(codeUnit.getDeclaringClassName(), codeUnit.getName(), codeUnit.getDescriptor(), tryCatchBlocks); + } + private > BUILDER filled(BUILDER builder, TargetInfo target) { return builder .withCaller(codeUnit) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java index de69e68e3c..47f8e97db0 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java @@ -33,6 +33,7 @@ import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodReferenceTarget; import com.tngtech.archunit.core.domain.ImportContext; +import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; @@ -57,6 +58,7 @@ import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodReferenceBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaParameterizedTypeBuilder; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit; import com.tngtech.archunit.core.importer.resolvers.ClassResolver; @@ -131,52 +133,66 @@ private , B extends RawAccessRecord> void tryProcess( } @Override - public Set createFieldAccessesFor(JavaCodeUnit codeUnit) { + public Set createFieldAccessesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (FieldAccessRecord record : processedFieldAccessRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaFieldAccessBuilder(), record) + JavaFieldAccess access = accessBuilderFrom(new JavaFieldAccessBuilder(), record) .withAccessType(record.getAccessType()) - .build()); + .build(); + result.add(access); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, access); } return result.build(); } @Override - public Set createMethodCallsFor(JavaCodeUnit codeUnit) { + public Set createMethodCallsFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedMethodCallRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaMethodCallBuilder(), record).build()); + JavaMethodCall call = accessBuilderFrom(new JavaMethodCallBuilder(), record).build(); + result.add(call); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, call); } return result.build(); } @Override - public Set createConstructorCallsFor(JavaCodeUnit codeUnit) { + public Set createConstructorCallsFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedConstructorCallRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaConstructorCallBuilder(), record).build()); + JavaConstructorCall call = accessBuilderFrom(new JavaConstructorCallBuilder(), record).build(); + result.add(call); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, call); } return result.build(); } @Override - public Set createMethodReferencesFor(JavaCodeUnit codeUnit) { + public Set createMethodReferencesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedMethodReferenceRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaMethodReferenceBuilder(), record).build()); + JavaMethodReference methodReference = accessBuilderFrom(new JavaMethodReferenceBuilder(), record).build(); + result.add(methodReference); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, methodReference); } return result.build(); } @Override - public Set createConstructorReferencesFor(JavaCodeUnit codeUnit) { + public Set createConstructorReferencesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); for (AccessRecord record : processedConstructorReferenceRecords.get(codeUnit)) { - result.add(accessBuilderFrom(new JavaConstructorReferenceBuilder(), record).build()); + JavaConstructorReference constructorReference = accessBuilderFrom(new JavaConstructorReferenceBuilder(), record).build(); + result.add(constructorReference); + handlePossibleTryBlockAccess(tryCatchBlockBuilders, record, constructorReference); } return result.build(); } + private void handlePossibleTryBlockAccess(Set tryCatchBlockBuilders, AccessRecord record, JavaAccess access) { + tryCatchBlockBuilders.forEach(builder -> builder.addIfContainedInTryBlock(record.getRaw(), access)); + } + private > B accessBuilderFrom(B builder, AccessRecord record) { return builder @@ -308,6 +324,11 @@ public Optional createEnclosingCodeUnit(JavaClass owner) { return enclosingClass.tryGetCodeUnitWithParameterTypeNames(codeUnit.getName(), codeUnit.getRawParameterTypeNames()); } + @Override + public Set createTryCatchBlockBuilders(JavaCodeUnit codeUnit) { + return importRecord.getTryCatchBlockBuildersFor(codeUnit); + } + @Override public JavaClass resolveClass(String fullyQualifiedClassName) { return classes.getOrResolve(fullyQualifiedClassName); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index ac5c5bdd54..184b7b0bb9 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -42,7 +42,9 @@ import com.tngtech.archunit.core.domain.AccessTarget.MethodReferenceTarget; import com.tngtech.archunit.core.domain.DomainObjectCreationContext; import com.tngtech.archunit.core.domain.Formatters; +import com.tngtech.archunit.core.domain.ImportContext; import com.tngtech.archunit.core.domain.InstanceofCheck; +import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassDescriptor; @@ -67,11 +69,14 @@ import com.tngtech.archunit.core.domain.JavaWildcardType; import com.tngtech.archunit.core.domain.ReferencedClassObject; import com.tngtech.archunit.core.domain.Source; +import com.tngtech.archunit.core.domain.SourceCodeLocation; import com.tngtech.archunit.core.domain.ThrowsClause; +import com.tngtech.archunit.core.domain.TryCatchBlock; import com.tngtech.archunit.core.domain.properties.HasTypeParameters; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Sets.union; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeTypeVariable; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createGenericArrayType; @@ -79,6 +84,7 @@ import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createReferencedClassObject; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createSource; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createThrowsClause; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createTryCatchBlock; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createTypeVariable; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createWildcardType; import static com.tngtech.archunit.core.domain.Formatters.ensureCanonicalArrayTypeName; @@ -937,6 +943,64 @@ static Set build( } } + @Internal + public static class TryCatchBlockBuilder { + private Set caughtThrowables; + private int lineNumber; + private JavaCodeUnit owner; + private ImportContext context; + private final Set> accessesContainedInTryBlock = new HashSet<>(); + private Set rawAccessesContainedInTryBlock; + + TryCatchBlockBuilder() { + } + + TryCatchBlockBuilder withCaughtThrowables(Set caughtThrowables) { + this.caughtThrowables = caughtThrowables; + return this; + } + + TryCatchBlockBuilder withLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + return this; + } + + TryCatchBlockBuilder withRawAccessesInTryBlock(Set accessRecords) { + this.rawAccessesContainedInTryBlock = accessRecords; + return this; + } + + void addIfContainedInTryBlock(RawAccessRecord rawRecord, JavaAccess access) { + if (rawAccessesContainedInTryBlock.contains(rawRecord)) { + accessesContainedInTryBlock.add(access); + } + } + + public TryCatchBlock build(JavaCodeUnit owner, ImportContext context) { + this.owner = owner; + this.context = context; + return createTryCatchBlock(this); + } + + public JavaCodeUnit getOwner() { + return owner; + } + + public Set> getAccessesContainedInTryBlock() { + return accessesContainedInTryBlock; + } + + public Set getCaughtThrowables() { + return caughtThrowables.stream() + .map(throwable -> context.resolveClass(throwable.getFullyQualifiedClassName())) + .collect(toImmutableSet()); + } + + public SourceCodeLocation getSourceCodeLocation() { + return SourceCodeLocation.of(owner.getOwner(), lineNumber); + } + } + @Internal public abstract static class JavaAccessBuilder> { private JavaCodeUnit origin; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java index 2fc7c34e9f..462d433c06 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java @@ -356,11 +356,17 @@ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, bo // NOTE: ASM does not reliably visit this method, so if this method is skipped, line number 0 is recorded @Override - public void visitLineNumber(int line, Label start) { - LOG.trace("Examining line number {}", line); + public void visitLineNumber(int line, Label label) { + LOG.trace("Examining line number {} at label {}", line, label); codeUnitBuilder.recordLineNumber(line); actualLineNumber = line; - accessHandler.setLineNumber(actualLineNumber); + accessHandler.onLineNumber(actualLineNumber, label); + } + + @Override + public void visitLabel(Label label) { + LOG.trace("Examining label {}", label); + accessHandler.onLabel(label); } @Override @@ -372,6 +378,15 @@ public void visitLdcInsn(Object value) { } } + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + if (type != null) { + accessHandler.handleTryCatchBlock(start, end, handler, JavaClassDescriptorImporter.createFromAsmObjectTypeName(type)); + } else { + accessHandler.handleTryFinallyBlock(start, end, handler); + } + } + @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { accessHandler.handleFieldInstruction(opcode, owner, name, desc); @@ -425,6 +440,7 @@ private void processLambdaMetafactoryMethodHandleArgument(Handle methodHandle) { @Override public void visitEnd() { declarationHandler.onDeclaredMemberAnnotations(codeUnitBuilder.getName(), codeUnitBuilder.getDescriptor(), annotations); + accessHandler.onMethodEnd(); } private static class AnnotationDefaultProcessor extends ClassAndPrimitiveDistinguishingAnnotationVisitor { @@ -512,12 +528,20 @@ interface AccessHandler { void setContext(CodeUnit codeUnit); - void setLineNumber(int lineNumber); + void onLineNumber(int lineNumber, Label label); + + void onLabel(Label label); void handleMethodInstruction(String owner, String name, String desc); void handleMethodReferenceInstruction(String owner, String name, String desc); + void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType); + + void handleTryFinallyBlock(Label start, Label end, Label handler); + + void onMethodEnd(); + @Internal class NoOp implements AccessHandler { @Override @@ -529,7 +553,11 @@ public void setContext(CodeUnit codeUnit) { } @Override - public void setLineNumber(int lineNumber) { + public void onLineNumber(int lineNumber, Label label) { + } + + @Override + public void onLabel(Label label) { } @Override @@ -539,6 +567,18 @@ public void handleMethodInstruction(String owner, String name, String desc) { @Override public void handleMethodReferenceInstruction(String owner, String name, String desc) { } + + @Override + public void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType) { + } + + @Override + public void handleTryFinallyBlock(Label start, Label end, Label handler) { + } + + @Override + public void onMethodEnd() { + } } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java index 4e1f6bbc6d..eb6ee6c7c0 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawAccessRecord.java @@ -91,15 +91,15 @@ private static List namesOf(Iterable descriptors) { return result.build(); } - public String getName() { + String getName() { return name; } - public List getRawParameterTypes() { + List getRawParameterTypes() { return rawParameterTypes; } - public List getRawParameterTypeNames() { + List getRawParameterTypeNames() { return rawParameterTypeNames; } @@ -107,6 +107,16 @@ String getDeclaringClassName() { return declaringClassName; } + String getDescriptor() { + return descriptor; + } + + boolean is(JavaCodeUnit method) { + return getName().equals(method.getName()) + && descriptor.equals(method.getDescriptor()) + && getDeclaringClassName().equals(method.getOwner().getName()); + } + @Override public int hashCode() { return hashCode; @@ -135,12 +145,6 @@ public String toString() { ", declaringClassName='" + declaringClassName + '\'' + '}'; } - - public boolean is(JavaCodeUnit method) { - return getName().equals(method.getName()) - && descriptor.equals(method.getDescriptor()) - && getDeclaringClassName().equals(method.getOwner().getName()); - } } static final class TargetInfo { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/TryCatchRecorder.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/TryCatchRecorder.java new file mode 100644 index 0000000000..3cf58ad470 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/TryCatchRecorder.java @@ -0,0 +1,179 @@ +/* + * Copyright 2014-2022 TNG Technology Consulting GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tngtech.archunit.core.importer; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder; +import org.objectweb.asm.Label; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; + +class TryCatchRecorder { + private static final Logger log = LoggerFactory.getLogger(TryCatchRecorder.class); + + private final TryCatchBlocksFinishedListener tryCatchBlocksFinishedListener; + private final Map> blocksByEndByStart = new HashMap<>(); + private final Multimap activeBlocksByEnd = HashMultimap.create(); + private final Set