aboutsummaryrefslogtreecommitdiffstats
path: root/bundle-plugin/src/main/java/com/yahoo/container/plugin/classanalysis/AnalyzeClassVisitor.java
blob: 6b8f3f6ba1235c85464654cd0ae1fa1f2cc9e941 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.plugin.classanalysis;

import com.yahoo.api.annotations.PublicApi;
import com.yahoo.container.plugin.classanalysis.Analyze.JdkVersionCheck;
import com.yahoo.osgi.annotation.ExportPackage;
import com.yahoo.osgi.annotation.Version;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

/**
 * Picks up classes used in class files.
 *
 * @author Tony Vaagenes
 * @author ollivir
 * @author gjoranv
 */
class AnalyzeClassVisitor extends ClassVisitor implements ImportCollector {

    private String name = null;
    private final Set<String> imports = new HashSet<>();
    private Optional<ExportPackageAnnotation> exportPackageAnnotation = Optional.empty();
    private boolean isPublicApi = false;

    private final Optional<ArtifactVersion> defaultExportPackageVersion;
    private final JdkVersionCheck jdkVersionCheck;

    AnalyzeClassVisitor(ArtifactVersion defaultExportPackageVersion, JdkVersionCheck jdkVersionCheck) {
        super(Opcodes.ASM9);
        this.defaultExportPackageVersion = Optional.ofNullable(defaultExportPackageVersion);
        this.jdkVersionCheck = jdkVersionCheck;
    }

    @Override
    public Set<String> imports() {
        return imports;
    }

    @Override
    public void visitAttribute(Attribute attribute) {
        addImport(Type.getObjectType(attribute.type));
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        Analyze.getClassName(Type.getReturnType(desc)).ifPresent(imports::add);
        Arrays.asList(Type.getArgumentTypes(desc)).forEach(argType -> Analyze.getClassName(argType).ifPresent(imports::add));
        if (exceptions != null) {
            Arrays.asList(exceptions).forEach(ex -> Analyze.internalNameToClassName(ex).ifPresent(imports::add));
        }

        AnalyzeSignatureVisitor.analyzeMethod(signature, this);
        return new AnalyzeMethodVisitor(this);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        Analyze.getClassName(Type.getType(desc)).ifPresent(imports::add);

        AnalyzeSignatureVisitor.analyzeField(signature, this);
        return new AnalyzeFieldVisitor(this);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        this.name = Analyze.internalNameToClassName(name)
                .orElseThrow(() -> new RuntimeException("Unable to resolve class name for " + name));

        if (version > Opcodes.V17 && jdkVersionCheck == JdkVersionCheck.ENABLED) {
            var jdkVersion = version - 44;
            throw new RuntimeException("Class " + name + " is compiled for Java version " + jdkVersion + ", but only Java 17 is supported");
        }

        addImportWithInternalName(superName);
        Arrays.asList(interfaces).forEach(this::addImportWithInternalName);

        AnalyzeSignatureVisitor.analyzeClass(signature, this);
    }

    @Override
    public void visitInnerClass(String name, String outerName, String innerName, int access) {
    }

    @Override
    public void visitOuterClass(String owner, String name, String desc) {
    }

    @Override
    public void visitSource(String source, String debug) {
    }

    @Override
    public void visitEnd() {
    }

    @SuppressWarnings("unchecked")
    private static <T> T defaultVersionValue(String name) {
        try {
            return (T) Version.class.getMethod(name).getDefaultValue();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Could not locate method " + name);
        }
    }

    private AnnotationVisitor visitExportPackage() {
        return new AnnotationVisitor(Opcodes.ASM9) {
            private int major = defaultExportPackageVersion.map(ArtifactVersion::getMajorVersion)
                    .orElse(defaultVersionValue("major"));
            private int minor = defaultExportPackageVersion.map(ArtifactVersion::getMinorVersion)
                    .orElse(defaultVersionValue("minor"));
            private int micro = defaultExportPackageVersion.map(ArtifactVersion::getIncrementalVersion)
                    .orElse(defaultVersionValue("micro"));

            // Default qualifier is the empty string.
            private String qualifier = defaultVersionValue("qualifier");

            @Override
            public void visit(String name, Object value) {
                if (name != null) {
                    switch (name) {
                        case "major" -> major = (int) value;
                        case "minor" -> minor = (int) value;
                        case "micro" -> micro = (int) value;
                        case "qualifier" -> qualifier = (String) value;
                    }
                }
            }

            @Override
            public void visitEnd() {
                exportPackageAnnotation = Optional.of(new ExportPackageAnnotation(major, minor, micro, qualifier));
            }

            @Override
            public void visitEnum(String name, String desc, String value) {
            }

            @Override
            public AnnotationVisitor visitArray(String name) {
                return this;
            }

            @Override
            public AnnotationVisitor visitAnnotation(String name, String desc) {
                return this;
            }
        };
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (ExportPackage.class.getName().equals(Type.getType(desc).getClassName())) {
            return visitExportPackage();
        } if (PublicApi.class.getName().equals(Type.getType(desc).getClassName())) {
            isPublicApi = true;
            return null;
        } else {
            if (visible) {
                addImportWithTypeDesc(desc);
            }
            return Analyze.visitAnnotationDefault(this);
        }
    }

    ClassFileMetaData result() {
        assert (!imports.contains("int"));
        var packageInfo = new PackageInfo(Packages.packageName(name), exportPackageAnnotation, isPublicApi);
        return new ClassFileMetaData(name, imports, packageInfo);
    }

}