Skip to content

Commit 06f94b2

Browse files
authored
fix inconsistent behavior with respect to primitive array types TNG#351
When we tried to resolve the component type of primitive array types (or other types missing from the import), we used `org.objectweb.asm.Type.getType(descriptor)` in some places, which unfortunately uses the canonical name as type name, instead of the class name. Then when the imported classes were checked for the type, e.g. only the type `[B` was present, but not the canonical name `byte[]` and consequently a new type `byte[]` was created that behaved inconsistently and could not return its component type. The correct handling of this case was actually already handled within `JavaType`, thus I have encapsulated all type parsing into this class and its facade `JavaTypeImporter` and written an architecture test to ensure that in the future only `JavaType` and `JavaTypeImporter` will be used to parse types from type names. On the way I noticed that `AnnotationTypeConversion` was actually misleading, it looked like a generic class to handle many different cases when in fact for each caller only one specific case was relevant. Thus I have inlined most of the cases where they are actually relevant (e.g. some places only cared to handle a type `Class[]` but did not care about `org.objectweb.asm.Type`, while the concrete conversion only cared about that case and not e.g. `Class`, etc.). Usually only one specific case was relevant and most of the time there was only one caller. The only thing left is the generic `convert(Object value)` method, since it is called from different places and encapsulates that we need to convert `org.objectweb.asm.Type` transparently, since we do not want to deal with this type anymore outside of `JavaType(Importer)`.
2 parents 72b5ad6 + db0088f commit 06f94b2

File tree

8 files changed

+113
-99
lines changed

8 files changed

+113
-99
lines changed

archunit-integration-test/src/test/java/com/tngtech/archunit/ArchUnitArchitectureTest.java

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
import com.tngtech.archunit.core.domain.JavaClass;
1414
import com.tngtech.archunit.core.domain.properties.HasOwner;
1515
import com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With;
16-
import com.tngtech.archunit.core.importer.ClassFileImporter;
17-
import com.tngtech.archunit.core.importer.DomainBuilders;
1816
import com.tngtech.archunit.core.importer.ImportOption;
1917
import com.tngtech.archunit.core.importer.Location;
2018
import com.tngtech.archunit.junit.AnalyzeClasses;
@@ -59,9 +57,7 @@ public class ArchUnitArchitectureTest {
5957
.whereLayer("Base").mayOnlyBeAccessedByLayers("Root", "Core", "Lang", "Library", "JUnit");
6058

6159
@ArchTest
62-
public static final ArchRule domain_does_not_access_importer =
63-
noClasses().that().resideInAPackage("..core.domain..")
64-
.should().accessClassesThat(belong_to_the_import_context());
60+
public static final ArchRules importer_rules = ArchRules.in(ImporterRules.class);
6561

6662
@ArchTest
6763
public static final ArchRule types_are_only_resolved_via_reflection_in_allowed_places =
@@ -70,18 +66,7 @@ public class ArchUnitArchitectureTest {
7066
.as("no classes should illegally resolve classes via reflection");
7167

7268
@ArchTest
73-
public static final ArchRules public_API_rules =
74-
ArchRules.in(PublicAPIRules.class);
75-
76-
private static DescribedPredicate<JavaClass> belong_to_the_import_context() {
77-
return new DescribedPredicate<JavaClass>("belong to the import context") {
78-
@Override
79-
public boolean apply(JavaClass input) {
80-
return input.getPackageName().startsWith(ClassFileImporter.class.getPackage().getName())
81-
&& !input.getName().contains(DomainBuilders.class.getSimpleName());
82-
}
83-
};
84-
}
69+
public static final ArchRules public_API_rules = ArchRules.in(PublicAPIRules.class);
8570

8671
private static DescribedPredicate<JavaCall<?>> typeIsIllegallyResolvedViaReflection() {
8772
DescribedPredicate<JavaCall<?>> explicitlyAllowedUsage =
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.tngtech.archunit;
2+
3+
import com.tngtech.archunit.base.DescribedPredicate;
4+
import com.tngtech.archunit.core.domain.JavaClass;
5+
import com.tngtech.archunit.core.domain.JavaType;
6+
import com.tngtech.archunit.core.importer.ClassFileImporter;
7+
import com.tngtech.archunit.core.importer.DomainBuilders;
8+
import com.tngtech.archunit.junit.ArchTest;
9+
import com.tngtech.archunit.lang.ArchRule;
10+
11+
import static com.tngtech.archunit.ArchUnitArchitectureTest.THIRDPARTY_PACKAGE_IDENTIFIER;
12+
import static com.tngtech.archunit.base.DescribedPredicate.not;
13+
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.belongToAnyOf;
14+
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
15+
16+
public class ImporterRules {
17+
18+
@ArchTest
19+
public static final ArchRule domain_does_not_access_importer =
20+
noClasses().that().resideInAPackage("..core.domain..")
21+
.should().accessClassesThat(belong_to_the_import_context());
22+
23+
@ArchTest
24+
public static final ArchRule ASM_type_is_only_accessed_within_JavaType_or_JavaTypeImporter =
25+
noClasses()
26+
.that().resideOutsideOfPackage(THIRDPARTY_PACKAGE_IDENTIFIER)
27+
.and(not(belongToAnyOf(JavaType.class)))
28+
.and().doNotHaveFullyQualifiedName("com.tngtech.archunit.core.importer.JavaTypeImporter")
29+
.should().dependOnClassesThat().haveNameMatching(".*\\.asm\\..*Type")
30+
.as("org.objectweb.asm.Type should only be accessed within JavaType(Importer)")
31+
.because("org.objectweb.asm.Type handles array types inconsistently (uses the canonical name instead of the class name), "
32+
+ "so the correct behavior is implemented only within JavaType");
33+
34+
private static DescribedPredicate<JavaClass> belong_to_the_import_context() {
35+
return new DescribedPredicate<JavaClass>("belong to the import context") {
36+
@Override
37+
public boolean apply(JavaClass input) {
38+
return input.getPackageName().startsWith(ClassFileImporter.class.getPackage().getName())
39+
&& !input.getName().contains(DomainBuilders.class.getSimpleName());
40+
}
41+
};
42+
}
43+
}

archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.tngtech.archunit.core.domain.JavaField;
3737
import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType;
3838
import com.tngtech.archunit.core.domain.JavaMethod;
39+
import com.tngtech.archunit.core.domain.JavaType;
3940
import com.tngtech.archunit.core.domain.properties.HasDescriptor;
4041
import com.tngtech.archunit.core.domain.properties.HasName;
4142
import com.tngtech.archunit.core.domain.properties.HasOwner;
@@ -44,7 +45,6 @@
4445
import com.tngtech.archunit.core.importer.DomainBuilders.MethodCallTargetBuilder;
4546
import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit;
4647
import com.tngtech.archunit.core.importer.RawAccessRecord.TargetInfo;
47-
import org.objectweb.asm.Type;
4848

4949
import static com.google.common.collect.Iterables.getOnlyElement;
5050
import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClassList;
@@ -167,7 +167,7 @@ public JavaCodeUnit getCaller() {
167167
public MethodCallTarget getTarget() {
168168
Supplier<Set<JavaMethod>> methodsSupplier = new MethodTargetSupplier(targetOwner.getAllMethods(), record.target);
169169
JavaClassList parameters = getArgumentTypesFrom(record.target.desc, classes);
170-
JavaClass returnType = classes.getOrResolve(Type.getReturnType(record.target.desc).getClassName());
170+
JavaClass returnType = classes.getOrResolve(JavaTypeImporter.importAsmMethodReturnType(record.target.desc).getName());
171171
return new MethodCallTargetBuilder()
172172
.withOwner(targetOwner)
173173
.withName(record.target.name)
@@ -224,7 +224,7 @@ public JavaCodeUnit getCaller() {
224224
@Override
225225
public FieldAccessTarget getTarget() {
226226
Supplier<Optional<JavaField>> fieldSupplier = new FieldTargetSupplier(targetOwner.getAllFields(), record.target);
227-
JavaClass fieldType = classes.getOrResolve(Type.getType(record.target.desc).getClassName());
227+
JavaClass fieldType = classes.getOrResolve(JavaTypeImporter.importAsmType(record.target.desc).getName());
228228
return new FieldAccessTargetBuilder()
229229
.withOwner(targetOwner)
230230
.withName(record.target.name)
@@ -290,8 +290,8 @@ private static <T> Optional<T> uniqueTargetIn(Collection<T> collection) {
290290

291291
private static JavaClassList getArgumentTypesFrom(String descriptor, ImportedClasses classes) {
292292
List<JavaClass> paramTypes = new ArrayList<>();
293-
for (Type type : Type.getArgumentTypes(descriptor)) {
294-
paramTypes.add(classes.getOrResolve(type.getClassName()));
293+
for (JavaType type : JavaTypeImporter.importAsmMethodArgumentTypes(descriptor)) {
294+
paramTypes.add(classes.getOrResolve(type.getName()));
295295
}
296296
return createJavaClassList(paramTypes);
297297
}

archunit/src/main/java/com/tngtech/archunit/core/importer/ImportedClasses.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ Map<String, JavaClass> getDirectlyImported() {
4848
return directlyImported;
4949
}
5050

51-
void add(JavaClass clazz) {
52-
additionalClasses.put(clazz.getName(), clazz);
53-
}
54-
5551
JavaClass getOrResolve(String typeName) {
5652
ensurePresent(typeName);
5753
return directlyImported.containsKey(typeName) ?

archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java

Lines changed: 34 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@
2121
import java.util.Collection;
2222
import java.util.HashSet;
2323
import java.util.List;
24-
import java.util.Map;
2524
import java.util.Set;
2625

2726
import com.google.common.collect.ImmutableList;
28-
import com.google.common.collect.ImmutableMap;
2927
import com.google.common.collect.ImmutableSet;
3028
import com.google.common.primitives.Booleans;
3129
import com.google.common.primitives.Bytes;
@@ -36,7 +34,6 @@
3634
import com.google.common.primitives.Longs;
3735
import com.google.common.primitives.Shorts;
3836
import com.tngtech.archunit.Internal;
39-
import com.tngtech.archunit.base.Function;
4037
import com.tngtech.archunit.base.HasDescription;
4138
import com.tngtech.archunit.base.Optional;
4239
import com.tngtech.archunit.core.MayResolveTypesViaReflection;
@@ -207,7 +204,7 @@ public FieldVisitor visitField(int access, String name, String desc, String sign
207204

208205
DomainBuilders.JavaFieldBuilder fieldBuilder = new DomainBuilders.JavaFieldBuilder()
209206
.withName(name)
210-
.withType(JavaTypeImporter.importAsmType(Type.getType(desc)))
207+
.withType(JavaTypeImporter.importAsmType(desc))
211208
.withModifiers(JavaModifier.getModifiersForField(access))
212209
.withDescriptor(desc);
213210
declarationHandler.onDeclaredField(fieldBuilder);
@@ -221,29 +218,21 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
221218
}
222219

223220
LOG.trace("Analyzing method {}.{}:{}", className, name, desc);
224-
accessHandler.setContext(new CodeUnit(name, namesOf(Type.getArgumentTypes(desc)), className));
221+
List<JavaType> parameters = JavaTypeImporter.importAsmMethodArgumentTypes(desc);
222+
accessHandler.setContext(new CodeUnit(name, namesOf(parameters), className));
225223

226224
DomainBuilders.JavaCodeUnitBuilder<?, ?> codeUnitBuilder = addCodeUnitBuilder(name);
227-
Type methodType = Type.getMethodType(desc);
228225
codeUnitBuilder
229226
.withName(name)
230227
.withModifiers(JavaModifier.getModifiersForMethod(access))
231-
.withParameters(typesFrom(methodType.getArgumentTypes()))
232-
.withReturnType(JavaTypeImporter.importAsmType(methodType.getReturnType()))
228+
.withParameters(parameters)
229+
.withReturnType(JavaTypeImporter.importAsmMethodReturnType(desc))
233230
.withDescriptor(desc)
234231
.withThrowsClause(typesFrom(exceptions));
235232

236233
return new MethodProcessor(className, accessHandler, codeUnitBuilder);
237234
}
238235

239-
private List<JavaType> typesFrom(Type[] asmTypes) {
240-
List<JavaType> result = new ArrayList<>();
241-
for (Type asmType : asmTypes) {
242-
result.add(JavaTypeImporter.importAsmType(asmType));
243-
}
244-
return result;
245-
}
246-
247236
private List<JavaType> typesFrom(String[] throwsDeclarations) {
248237
List<JavaType> result = new ArrayList<>();
249238
if (throwsDeclarations != null) {
@@ -289,10 +278,10 @@ public void visitEnd() {
289278
LOG.trace("Done analyzing {}", className);
290279
}
291280

292-
private static List<String> namesOf(Type[] types) {
281+
private static List<String> namesOf(Iterable<JavaType> types) {
293282
ImmutableList.Builder<String> result = ImmutableList.builder();
294-
for (Type type : types) {
295-
result.add(JavaTypeImporter.importAsmType(type).getName());
283+
for (JavaType type : types) {
284+
result.add(type.getName());
296285
}
297286
return result.build();
298287
}
@@ -487,7 +476,7 @@ public void visitEnd() {
487476
}
488477

489478
private static DomainBuilders.JavaAnnotationBuilder annotationBuilderFor(String desc) {
490-
return new DomainBuilders.JavaAnnotationBuilder().withType(JavaTypeImporter.importAsmType(Type.getType(desc)));
479+
return new DomainBuilders.JavaAnnotationBuilder().withType(JavaTypeImporter.importAsmType(desc));
491480
}
492481

493482
private static class AnnotationProcessor extends AnnotationVisitor {
@@ -575,7 +564,11 @@ private AnnotationArrayProcessor(AnnotationArrayContext annotationArrayContext)
575564

576565
@Override
577566
public void visit(String name, Object value) {
578-
setDerivedComponentType(value);
567+
if (value instanceof Type) {
568+
setDerivedComponentType(JavaClass.class);
569+
} else {
570+
setDerivedComponentType(value.getClass());
571+
}
579572
values.add(AnnotationTypeConversion.convert(value));
580573
}
581574

@@ -596,16 +589,11 @@ public void visitEnum(String name, final String desc, final String value) {
596589
values.add(javaEnumBuilder(desc, value));
597590
}
598591

599-
private void setDerivedComponentType(Object value) {
600-
setDerivedComponentType(value.getClass());
601-
}
602-
603592
// NOTE: If the declared annotation is not imported itself, we can still use this heuristic,
604593
// to determine additional information about the respective array.
605594
// (It the annotation is imported itself, we could easily determine this from the respective
606595
// JavaClass methods)
607596
private void setDerivedComponentType(Class<?> type) {
608-
type = AnnotationTypeConversion.convert(type);
609597
checkState(derivedComponentType == null || derivedComponentType.equals(type),
610598
"Found mixed component types while importing array, this is most likely a bug");
611599

@@ -668,22 +656,21 @@ private Optional<Class<?>> determineComponentType(ClassesByTypeName importedClas
668656
.tryGetMethod(annotationArrayContext.getDeclaringAnnotationMemberName());
669657

670658
return method.isPresent() ?
671-
determineComponentTypeFromReturnValue(method) :
659+
determineComponentTypeFromReturnValue(method.get()) :
672660
Optional.<Class<?>>absent();
673661
}
674662

675-
private Optional<Class<?>> determineComponentTypeFromReturnValue(Optional<JavaMethod> method) {
676-
String name = method.get().getRawReturnType().getName();
677-
Optional<Class<?>> result = AnnotationTypeConversion.tryConvert(name);
678-
if (result.isPresent()) {
679-
return Optional.<Class<?>>of(result.get().getComponentType());
663+
private Optional<Class<?>> determineComponentTypeFromReturnValue(JavaMethod method) {
664+
if (method.getRawReturnType().isEquivalentTo(Class[].class)) {
665+
return Optional.<Class<?>>of(JavaClass.class);
680666
}
681-
return resolveComponentTypeFrom(name);
667+
668+
return resolveComponentTypeFrom(method.getRawReturnType().getName());
682669
}
683670

684671
@MayResolveTypesViaReflection(reason = "Resolving primitives does not really use reflection")
685-
private Optional<Class<?>> resolveComponentTypeFrom(String name) {
686-
JavaType type = JavaType.From.name(name);
672+
private Optional<Class<?>> resolveComponentTypeFrom(String arrayTypeName) {
673+
JavaType type = JavaType.From.name(arrayTypeName);
687674
JavaType componentType = getComponentType(type);
688675

689676
if (componentType.isPrimitive()) {
@@ -725,50 +712,25 @@ private static ValueBuilder javaEnumBuilder(final String desc, final String valu
725712
public <T extends HasDescription> Optional<Object> build(T owner, ClassesByTypeName importedClasses) {
726713
return Optional.<Object>of(
727714
new DomainBuilders.JavaEnumConstantBuilder()
728-
.withDeclaringClass(importedClasses.get(Type.getType(desc).getClassName()))
715+
.withDeclaringClass(importedClasses.get(JavaTypeImporter.importAsmType(desc).getName()))
729716
.withName(value)
730717
.build());
731718
}
732719
};
733720
}
734721

735722
private static class AnnotationTypeConversion {
736-
private static final Map<String, Class<?>> externalTypeToInternalType = ImmutableMap.of(
737-
Type.class.getName(), JavaClass.class,
738-
Class.class.getName(), JavaClass.class,
739-
Class[].class.getName(), JavaClass[].class
740-
);
741-
private static final Map<Class<?>, Function<Object, ValueBuilder>> importedValueToInternalValue =
742-
ImmutableMap.<Class<?>, Function<Object, ValueBuilder>>of(
743-
Type.class, new Function<Object, ValueBuilder>() {
744-
@Override
745-
public ValueBuilder apply(final Object input) {
746-
return new ValueBuilder() {
747-
@Override
748-
public <T extends HasDescription> Optional<Object> build(T owner, ClassesByTypeName importedClasses) {
749-
return Optional.<Object>of(importedClasses.get(((Type) input).getClassName()));
750-
}
751-
};
752-
}
753-
}
754-
);
755-
756-
static Class<?> convert(Class<?> type) {
757-
return externalTypeToInternalType.containsKey(type.getName()) ?
758-
externalTypeToInternalType.get(type.getName()) :
759-
type;
760-
}
761-
762-
static Optional<Class<?>> tryConvert(String typeName) {
763-
return externalTypeToInternalType.containsKey(typeName) ?
764-
Optional.<Class<?>>of(externalTypeToInternalType.get(typeName)) :
765-
Optional.<Class<?>>absent();
766-
}
767-
768-
static ValueBuilder convert(Object value) {
769-
return importedValueToInternalValue.containsKey(value.getClass()) ?
770-
importedValueToInternalValue.get(value.getClass()).apply(value) :
771-
ValueBuilder.ofFinished(value);
723+
static ValueBuilder convert(Object input) {
724+
final Object value = JavaTypeImporter.importAsmTypeIfPossible(input);
725+
if (value instanceof JavaType) {
726+
return new ValueBuilder() {
727+
@Override
728+
public <T extends HasDescription> Optional<Object> build(T owner, ClassesByTypeName importedClasses) {
729+
return Optional.<Object>of(importedClasses.get(((JavaType) value).getName()));
730+
}
731+
};
732+
}
733+
return ValueBuilder.ofFinished(value);
772734
}
773735
}
774736
}

archunit/src/main/java/com/tngtech/archunit/core/importer/JavaTypeImporter.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package com.tngtech.archunit.core.importer;
1717

18+
import java.util.List;
19+
20+
import com.google.common.collect.ImmutableList;
1821
import com.tngtech.archunit.core.domain.JavaType;
1922
import org.objectweb.asm.Type;
2023

@@ -30,4 +33,24 @@ static JavaType createFromAsmObjectTypeName(String objectTypeName) {
3033
static JavaType importAsmType(Type type) {
3134
return JavaType.From.name(type.getClassName());
3235
}
36+
37+
static Object importAsmTypeIfPossible(Object value) {
38+
return value instanceof Type ? importAsmType((Type) value) : value;
39+
}
40+
41+
static JavaType importAsmType(String typeDescriptor) {
42+
return importAsmType(Type.getType(typeDescriptor));
43+
}
44+
45+
static List<JavaType> importAsmMethodArgumentTypes(String methodDescriptor) {
46+
ImmutableList.Builder<JavaType> result = ImmutableList.builder();
47+
for (Type type : Type.getArgumentTypes(methodDescriptor)) {
48+
result.add(importAsmType(type));
49+
}
50+
return result.build();
51+
}
52+
53+
static JavaType importAsmMethodReturnType(String methodDescriptor) {
54+
return importAsmType(Type.getReturnType(methodDescriptor));
55+
}
3356
}

archunit/src/main/java/com/tngtech/archunit/core/importer/resolvers/ClassResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public Optional<JavaClass> tryResolve(String typeName) {
183183
}
184184
}
185185

186-
private abstract class ClassResolverProvider {
186+
private abstract static class ClassResolverProvider {
187187
private final Function<Exception, ClassResolverConfigurationException> onFailure;
188188

189189
ClassResolverProvider(Function<Exception, ClassResolverConfigurationException> onFailure) {

0 commit comments

Comments
 (0)