Skip to content

Commit fe72d92

Browse files
committed
add referenced class objects to JavaClass
To detect usages of class objects we need to look which classes are referenced in the constant pool. So far the following usage of `SomeClass` would not have been detected by ArchUnit: ``` class Example { private Map<Class<?>, Value> map = Map.of(SomeClass.class, someValue); } ``` In other words, pure usages of `Foo.class` could not be detected, because there was no "access" to `Foo` in the bytecode. However, for each such occurrence the class would actually be present in the constant pool of the referring class. While ASM does not allow direct access to the constant pool, it does allow us to hook into `ldc` instructions, i.e. load constant instructions in the bytecode, that will in turn reference the respective class. The way to detect this is principally an `instanceof` check for `org.objectweb.asm.Type` in `void visitLdcInsn(Object value)`. As far as I could see, any reference of another class as a constant would cause this method to be called with the respective ASM `Type` object. It would actually be possible to import a lot more constants here. I have looked a little into adding all supported constant types, so it would e.g. be possible to have assertions on `String` values, but then decided to let it go for now. Strings would still be simple, but as soon as `Integer` comes into play (which could also be imported), there are a lot of internal optimizations by the JVM (e.g. `iconst_1`, ...), which makes it hard to do this in a consistent way. I think the most valuable feature by far is to detect constant loads of classes (since those cause dependencies), so I decided to keep it simple for now. Signed-off-by: Peter Gafert <[email protected]>
1 parent 1b00986 commit fe72d92

File tree

13 files changed

+368
-4
lines changed

13 files changed

+368
-4
lines changed

archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java

+4
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ public static Source createSource(URI uri, Optional<String> sourceFileName, bool
146146
return new Source(uri, sourceFileName, md5InClassSourcesEnabled);
147147
}
148148

149+
public static ReferencedClassObject createReferencedClassObject(JavaCodeUnit codeUnit, JavaClass javaClass, int lineNumber) {
150+
return ReferencedClassObject.from(codeUnit, javaClass, lineNumber);
151+
}
152+
149153
public static <CODE_UNIT extends JavaCodeUnit> ThrowsClause<CODE_UNIT> createThrowsClause(CODE_UNIT codeUnit, List<JavaClass> types) {
150154
return ThrowsClause.from(codeUnit, types);
151155
}

archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java

+9
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,15 @@ public List<JavaTypeVariable<JavaClass>> getTypeParameters() {
648648
return typeParameters;
649649
}
650650

651+
@PublicAPI(usage = ACCESS)
652+
public Set<ReferencedClassObject> getReferencedClassObjects() {
653+
ImmutableSet.Builder<ReferencedClassObject> result = ImmutableSet.builder();
654+
for (JavaCodeUnit codeUnit : codeUnits) {
655+
result.addAll(codeUnit.getReferencedClassObjects());
656+
}
657+
return result.build();
658+
}
659+
651660
@Override
652661
@PublicAPI(usage = ACCESS)
653662
public JavaClass toErasure() {

archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public abstract class JavaCodeUnit extends JavaMember implements HasParameterTyp
5050
private final JavaClass returnType;
5151
private final JavaClassList parameters;
5252
private final String fullName;
53+
private final Set<ReferencedClassObject> referencedClassObjects;
5354
private final Set<InstanceofCheck> instanceofChecks;
5455

5556
private Set<JavaFieldAccess> fieldAccesses = Collections.emptySet();
@@ -61,7 +62,8 @@ public abstract class JavaCodeUnit extends JavaMember implements HasParameterTyp
6162
this.returnType = builder.getReturnType();
6263
this.parameters = builder.getParameters();
6364
fullName = formatMethod(getOwner().getName(), getName(), getRawParameterTypes());
64-
instanceofChecks = builder.getInstanceofChecks(this);
65+
referencedClassObjects = ImmutableSet.copyOf(builder.getReferencedClassObjects(this));
66+
instanceofChecks = ImmutableSet.copyOf(builder.getInstanceofChecks(this));
6567
}
6668

6769
/**
@@ -112,6 +114,11 @@ public Set<JavaConstructorCall> getConstructorCallsFromSelf() {
112114
return constructorCalls;
113115
}
114116

117+
@PublicAPI(usage = ACCESS)
118+
public Set<ReferencedClassObject> getReferencedClassObjects() {
119+
return referencedClassObjects;
120+
}
121+
115122
@PublicAPI(usage = ACCESS)
116123
public Set<InstanceofCheck> getInstanceofChecks() {
117124
return instanceofChecks;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2014-2021 TNG Technology Consulting GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.tngtech.archunit.core.domain;
17+
18+
import com.tngtech.archunit.PublicAPI;
19+
import com.tngtech.archunit.base.ChainableFunction;
20+
import com.tngtech.archunit.core.domain.properties.HasOwner;
21+
import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation;
22+
import com.tngtech.archunit.core.domain.properties.HasType;
23+
24+
import static com.google.common.base.MoreObjects.toStringHelper;
25+
import static com.google.common.base.Preconditions.checkNotNull;
26+
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
27+
28+
@PublicAPI(usage = ACCESS)
29+
public final class ReferencedClassObject implements HasType, HasOwner<JavaCodeUnit>, HasSourceCodeLocation {
30+
private final JavaCodeUnit owner;
31+
private final JavaClass value;
32+
private final int lineNumber;
33+
private final SourceCodeLocation sourceCodeLocation;
34+
35+
private ReferencedClassObject(JavaCodeUnit owner, JavaClass value, int lineNumber) {
36+
this.owner = checkNotNull(owner);
37+
this.value = checkNotNull(value);
38+
this.lineNumber = lineNumber;
39+
sourceCodeLocation = SourceCodeLocation.of(owner.getOwner(), lineNumber);
40+
}
41+
42+
@Override
43+
@PublicAPI(usage = ACCESS)
44+
public JavaType getType() {
45+
return getRawType();
46+
}
47+
48+
@Override
49+
@PublicAPI(usage = ACCESS)
50+
public JavaClass getRawType() {
51+
return value;
52+
}
53+
54+
@Override
55+
@PublicAPI(usage = ACCESS)
56+
public JavaCodeUnit getOwner() {
57+
return owner;
58+
}
59+
60+
@PublicAPI(usage = ACCESS)
61+
public JavaClass getValue() {
62+
return value;
63+
}
64+
65+
@PublicAPI(usage = ACCESS)
66+
public int getLineNumber() {
67+
return lineNumber;
68+
}
69+
70+
@Override
71+
@PublicAPI(usage = ACCESS)
72+
public SourceCodeLocation getSourceCodeLocation() {
73+
return sourceCodeLocation;
74+
}
75+
76+
@Override
77+
public String toString() {
78+
return toStringHelper(this)
79+
.add("owner", owner)
80+
.add("value", value)
81+
.add("sourceCodeLocation", sourceCodeLocation)
82+
.toString();
83+
}
84+
85+
static ReferencedClassObject from(JavaCodeUnit owner, JavaClass javaClass, int lineNumber) {
86+
return new ReferencedClassObject(owner, javaClass, lineNumber);
87+
}
88+
89+
@PublicAPI(usage = ACCESS)
90+
public static final class Functions {
91+
private Functions() {
92+
}
93+
94+
@PublicAPI(usage = ACCESS)
95+
public static final ChainableFunction<ReferencedClassObject, JavaClass> GET_VALUE = new ChainableFunction<ReferencedClassObject, JavaClass>() {
96+
@Override
97+
public JavaClass apply(ReferencedClassObject input) {
98+
return input.getValue();
99+
}
100+
};
101+
}
102+
}

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

+16
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import com.tngtech.archunit.core.domain.JavaType;
6060
import com.tngtech.archunit.core.domain.JavaTypeVariable;
6161
import com.tngtech.archunit.core.domain.JavaWildcardType;
62+
import com.tngtech.archunit.core.domain.ReferencedClassObject;
6263
import com.tngtech.archunit.core.domain.Source;
6364
import com.tngtech.archunit.core.domain.ThrowsClause;
6465

@@ -67,6 +68,7 @@
6768
import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeTypeVariable;
6869
import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createInstanceofCheck;
6970
import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClassList;
71+
import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createReferencedClassObject;
7072
import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createSource;
7173
import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createThrowsClause;
7274
import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createTypeVariable;
@@ -217,6 +219,7 @@ public abstract static class JavaCodeUnitBuilder<OUTPUT, SELF extends JavaCodeUn
217219
private JavaClassDescriptor returnType;
218220
private List<JavaClassDescriptor> parameters;
219221
private List<JavaClassDescriptor> throwsDeclarations;
222+
private final Set<RawReferencedClassObject> rawReferencedClassObjects = new HashSet<>();
220223
private final List<RawInstanceofCheck> instanceOfChecks = new ArrayList<>();
221224

222225
private JavaCodeUnitBuilder() {
@@ -237,6 +240,11 @@ SELF withThrowsClause(List<JavaClassDescriptor> throwsDeclarations) {
237240
return self();
238241
}
239242

243+
SELF addReferencedClassObject(RawReferencedClassObject rawReferencedClassObject) {
244+
rawReferencedClassObjects.add(rawReferencedClassObject);
245+
return self();
246+
}
247+
240248
SELF addInstanceOfCheck(RawInstanceofCheck rawInstanceOfChecks) {
241249
this.instanceOfChecks.add(rawInstanceOfChecks);
242250
return self();
@@ -262,6 +270,14 @@ public <CODE_UNIT extends JavaCodeUnit> ThrowsClause<CODE_UNIT> getThrowsClause(
262270
return createThrowsClause(codeUnit, asJavaClasses(this.throwsDeclarations));
263271
}
264272

273+
public Set<ReferencedClassObject> getReferencedClassObjects(JavaCodeUnit codeUnit) {
274+
ImmutableSet.Builder<ReferencedClassObject> result = ImmutableSet.builder();
275+
for (RawReferencedClassObject rawReferencedClassObject : this.rawReferencedClassObjects) {
276+
result.add(createReferencedClassObject(codeUnit, get(rawReferencedClassObject.getClassName()), rawReferencedClassObject.getLineNumber()));
277+
}
278+
return result.build();
279+
}
280+
265281
public Set<InstanceofCheck> getInstanceofChecks(JavaCodeUnit codeUnit) {
266282
ImmutableSet.Builder<InstanceofCheck> result = ImmutableSet.builder();
267283
for (RawInstanceofCheck instanceOfCheck : this.instanceOfChecks) {

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@ class JavaClassDescriptorImporter {
2727
* i.e. java/lang/Object (note that this is not a descriptor like Ljava/lang/Object;)
2828
*/
2929
static JavaClassDescriptor createFromAsmObjectTypeName(String objectTypeName) {
30-
return importAsmType(Type.getObjectType(objectTypeName));
30+
return JavaClassDescriptor.From.name(Type.getObjectType(objectTypeName).getClassName());
3131
}
3232

33-
static JavaClassDescriptor importAsmType(Type type) {
33+
static JavaClassDescriptor importAsmType(Object type) {
34+
return importAsmType((Type) type);
35+
}
36+
37+
private static JavaClassDescriptor importAsmType(Type type) {
3438
return JavaClassDescriptor.From.name(type.getClassName());
3539
}
3640

@@ -39,7 +43,7 @@ static boolean isAsmType(Object value) {
3943
}
4044

4145
static Object importAsmTypeIfPossible(Object value) {
42-
return isAsmType(value) ? importAsmType((Type) value) : value;
46+
return isAsmType(value) ? importAsmType(value) : value;
4347
}
4448

4549
static JavaClassDescriptor importAsmTypeFromDescriptor(String typeDescriptor) {

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

+8
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,14 @@ public void visitLineNumber(int line, Label start) {
318318
accessHandler.setLineNumber(actualLineNumber);
319319
}
320320

321+
@Override
322+
public void visitLdcInsn(Object value) {
323+
if (JavaClassDescriptorImporter.isAsmType(value)) {
324+
codeUnitBuilder.addReferencedClassObject(
325+
RawReferencedClassObject.from(JavaClassDescriptorImporter.importAsmType(value), actualLineNumber));
326+
}
327+
}
328+
321329
@Override
322330
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
323331
accessHandler.handleFieldInstruction(opcode, owner, name, desc);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2014-2021 TNG Technology Consulting GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.tngtech.archunit.core.importer;
17+
18+
import com.tngtech.archunit.core.domain.JavaClassDescriptor;
19+
20+
import static com.google.common.base.MoreObjects.toStringHelper;
21+
import static com.google.common.base.Preconditions.checkNotNull;
22+
23+
class RawReferencedClassObject {
24+
private final JavaClassDescriptor type;
25+
private final int lineNumber;
26+
27+
private RawReferencedClassObject(JavaClassDescriptor type, int lineNumber) {
28+
this.type = checkNotNull(type);
29+
this.lineNumber = lineNumber;
30+
}
31+
32+
static RawReferencedClassObject from(JavaClassDescriptor target, int lineNumber) {
33+
return new RawReferencedClassObject(target, lineNumber);
34+
}
35+
36+
String getClassName() {
37+
return type.getFullyQualifiedClassName();
38+
}
39+
40+
int getLineNumber() {
41+
return lineNumber;
42+
}
43+
44+
@Override
45+
public String toString() {
46+
return toStringHelper(this)
47+
.add("type", type)
48+
.add("lineNumber", lineNumber)
49+
.toString();
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.tngtech.archunit.core.domain;
2+
3+
import java.io.File;
4+
5+
import com.tngtech.archunit.core.importer.ClassFileImporter;
6+
import org.junit.Test;
7+
8+
import static com.google.common.collect.Iterables.getOnlyElement;
9+
import static com.tngtech.archunit.core.domain.ReferencedClassObject.Functions.GET_VALUE;
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
12+
public class ReferencedClassObjectTest {
13+
14+
@Test
15+
public void function_getValue() {
16+
class SomeClass {
17+
@SuppressWarnings("unused")
18+
Class<?> call() {
19+
return File.class;
20+
}
21+
}
22+
23+
JavaClasses classes = new ClassFileImporter().importClasses(SomeClass.class, File.class);
24+
JavaMethod owner = classes.get(SomeClass.class).getMethod("call");
25+
26+
ReferencedClassObject referencedClassObject = getOnlyElement(owner.getReferencedClassObjects());
27+
28+
assertThat(GET_VALUE.apply(referencedClassObject)).isEqualTo(classes.get(File.class));
29+
}
30+
}

0 commit comments

Comments
 (0)