public record ParserVerifier()

in src/java.base/share/classes/jdk/internal/classfile/impl/verifier/ParserVerifier.java [53:512]


public record ParserVerifier(ClassModel classModel) {

    List<VerifyError> verify() {
        var errors = new ArrayList<VerifyError>();
        verifyConstantPool(errors);
        verifyInterfaces(errors);
        verifyFields(errors);
        verifyMethods(errors);
        verifyAttributes(classModel, errors);
        return errors;
    }

    private void verifyConstantPool(List<VerifyError> errors) {
        for (var cpe : classModel.constantPool()) {
            try {
                switch (cpe) {
                    case DoubleEntry de -> de.doubleValue();
                    case FloatEntry fe -> fe.floatValue();
                    case IntegerEntry ie -> ie.intValue();
                    case LongEntry le -> le.longValue();
                    case Utf8Entry ue -> ue.stringValue();
                    case ConstantDynamicEntry cde -> cde.asSymbol();
                    case InvokeDynamicEntry ide -> ide.asSymbol();
                    case ClassEntry ce -> ce.asSymbol();
                    case StringEntry se -> se.stringValue();
                    case MethodHandleEntry mhe -> mhe.asSymbol();
                    case MethodTypeEntry mte -> mte.asSymbol();
                    case FieldRefEntry fre -> {
                        try {
                            fre.owner().asSymbol();
                        } catch (VerifyError|Exception e) {
                            errors.add(cpeVerifyError(cpe, e));
                        }
                        try {
                            fre.typeSymbol();
                        } catch (VerifyError|Exception e) {
                            errors.add(cpeVerifyError(cpe, e));
                        }
                        verifyFieldName(fre.name().stringValue());
                    }
                    case InterfaceMethodRefEntry imre -> {
                        try {
                            imre.owner().asSymbol();
                        } catch (VerifyError|Exception e) {
                            errors.add(cpeVerifyError(cpe, e));
                        }
                        try {
                            imre.typeSymbol();
                        } catch (VerifyError|Exception e) {
                            errors.add(cpeVerifyError(cpe, e));
                        }
                        verifyMethodName(imre.name().stringValue());
                    }
                    case MethodRefEntry mre -> {
                        try {
                            mre.owner().asSymbol();
                        } catch (VerifyError|Exception e) {
                            errors.add(cpeVerifyError(cpe, e));
                        }
                        try {
                            mre.typeSymbol();
                        } catch (VerifyError|Exception e) {
                            errors.add(cpeVerifyError(cpe, e));
                        }
                        verifyMethodName(mre.name().stringValue());
                    }
                    case ModuleEntry me -> me.asSymbol();
                    case NameAndTypeEntry nate -> {
                        try {
                            nate.name().stringValue();
                        } catch (VerifyError|Exception e) {
                            errors.add(cpeVerifyError(cpe, e));
                        }
                        nate.type().stringValue();
                    }
                    case PackageEntry pe -> pe.asSymbol();
                }
            } catch (VerifyError|Exception e) {
                errors.add(cpeVerifyError(cpe, e));
            }
        }
    }

    private VerifyError cpeVerifyError(final PoolEntry cpe, final Throwable e) {
        return new VerifyError("%s at constant pool index %d in %s".formatted(e.getMessage(), cpe.index(), toString(classModel)));
    }

    private void verifyFieldName(String name) {
        if (name.length() == 0 || name.chars().anyMatch(ch -> switch(ch) {
                    case '.', ';', '[', '/' -> true;
                    default -> false;
                })) {
              throw new VerifyError("Illegal field name %s in %s".formatted(name, toString(classModel)));
        }
    }

    private void verifyMethodName(String name) {
        if (!name.equals(INIT_NAME)
            && !name.equals(CLASS_INIT_NAME)
            && (name.length() == 0 || name.chars().anyMatch(ch -> switch(ch) {
                    case '.', ';', '[', '/', '<', '>' -> true;
                    default -> false;
                }))) {
              throw new VerifyError("Illegal method name %s in %s".formatted(name, toString(classModel)));
        }
    }

    private void verifyInterfaces(List<VerifyError> errors) {
        var intfs = new HashSet<ClassEntry>();
        for (var intf : classModel.interfaces()) {
            if (!intfs.add(intf)) {
                errors.add(new VerifyError("Duplicate interface %s in %s".formatted(intf.asSymbol().displayName(), toString(classModel))));
            }
        }
    }

    private void verifyFields(List<VerifyError> errors) {
        record F(Utf8Entry name, Utf8Entry type) {};
        var fields = new HashSet<F>();
        for (var f : classModel.fields()) try {
            if (!fields.add(new F(f.fieldName(), f.fieldType()))) {
                errors.add(new VerifyError("Duplicate field name %s with signature %s in %s".formatted(f.fieldName().stringValue(), f.fieldType().stringValue(), toString(classModel))));
            }
            verifyFieldName(f.fieldName().stringValue());
        } catch (VerifyError ve) {
            errors.add(ve);
        }
    }

    private void verifyMethods(List<VerifyError> errors) {
        record M(Utf8Entry name, Utf8Entry type) {};
        var methods = new HashSet<M>();
        for (var m : classModel.methods()) try {
            if (!methods.add(new M(m.methodName(), m.methodType()))) {
                errors.add(new VerifyError("Duplicate method name %s with signature %s in %s".formatted(m.methodName().stringValue(), m.methodType().stringValue(), toString(classModel))));
            }
            if (m.methodName().equalsString(CLASS_INIT_NAME)
                    && !m.flags().has(AccessFlag.STATIC)) {
                errors.add(new VerifyError("Method <clinit> is not static in %s".formatted(toString(classModel))));
            }
            if (classModel.flags().has(AccessFlag.INTERFACE)
                    && m.methodName().equalsString(INIT_NAME)) {
                errors.add(new VerifyError("Interface cannot have a method named <init> in %s".formatted(toString(classModel))));
            }
            verifyMethodName(m.methodName().stringValue());
        } catch (VerifyError ve) {
            errors.add(ve);
        }
    }

    private void verifyAttributes(ClassFileElement cfe, List<VerifyError> errors) {
        if (cfe instanceof AttributedElement ae) {
            var attrNames = new HashSet<String>();
            for (var a : ae.attributes()) {
                if (!a.attributeMapper().allowMultiple() && !attrNames.add(a.attributeName().stringValue())) {
                    errors.add(new VerifyError("Multiple %s attributes in %s".formatted(a.attributeName().stringValue(), toString(ae))));
                }
                verifyAttribute(ae, a, errors);
            }
        }
        switch (cfe) {
            case CompoundElement<?> comp -> {
                for (var e : comp) verifyAttributes(e, errors);
            }
            case RecordAttribute ra -> {
                for(var rc : ra.components()) verifyAttributes(rc, errors);
            }
            default -> {}
        }
    }

    private void verifyAttribute(AttributedElement ae, Attribute<?> a, List<VerifyError> errors) {
        int size = switch (a) {
            case AnnotationDefaultAttribute aa ->
                valueSize(aa.defaultValue());
            case BootstrapMethodsAttribute bma ->
                2 + bma.bootstrapMethods().stream().mapToInt(bm -> 4 + 2 * bm.arguments().size()).sum();
            case CharacterRangeTableAttribute cra ->
                2 + 14 * cra.characterRangeTable().size();
            case CodeAttribute ca -> {
                MethodModel mm = (MethodModel)ae;
                if (mm.flags().has(AccessFlag.NATIVE) || mm.flags().has(AccessFlag.ABSTRACT)) {
                    errors.add(new VerifyError("Code attribute in native or abstract %s".formatted(toString(ae))));
                }
                if (ca.maxLocals() < Util.maxLocals(mm.flags().flagsMask(), mm.methodTypeSymbol())) {
                    errors.add(new VerifyError("Arguments can't fit into locals in %s".formatted(toString(ae))));
                }
                yield 10 + ca.codeLength() + 8 * ca.exceptionHandlers().size() + attributesSize(ca.attributes());
            }
            case CompilationIDAttribute cida -> {
                cida.compilationId();
                yield 2;
            }
            case ConstantValueAttribute cva -> {
                ClassDesc type = ((FieldModel)ae).fieldTypeSymbol();
                ConstantValueEntry cve = cva.constant();
                if (!switch (TypeKind.from(type)) {
                    case BOOLEAN, BYTE, CHAR, INT, SHORT -> cve instanceof IntegerEntry;
                    case DOUBLE -> cve instanceof DoubleEntry;
                    case FLOAT -> cve instanceof FloatEntry;
                    case LONG -> cve instanceof LongEntry;
                    case REFERENCE -> type.equals(ConstantDescs.CD_String) && cve instanceof StringEntry;
                    case VOID -> false;
                }) {
                    errors.add(new VerifyError("Bad constant value type in %s".formatted(toString(ae))));
                }
                yield 2;
            }
            case DeprecatedAttribute _ ->
                0;
            case EnclosingMethodAttribute ema -> {
                ema.enclosingClass();
                ema.enclosingMethod();
                yield 4;
            }
            case ExceptionsAttribute ea ->
                2 + 2 * ea.exceptions().size();
            case InnerClassesAttribute ica -> {
                for (var ici : ica.classes()) {
                    if (ici.outerClass().isPresent() && ici.outerClass().get().equals(ici.innerClass())) {
                        errors.add(new VerifyError("Class is both outer and inner class in %s".formatted(toString(ae))));
                    }
                }
                yield 2 + 8 * ica.classes().size();
            }
            case LineNumberTableAttribute lta ->
                2 + 4 * lta.lineNumbers().size();
            case LocalVariableTableAttribute lvta ->
                2 + 10 * lvta.localVariables().size();
            case LocalVariableTypeTableAttribute lvta ->
                2 + 10 * lvta.localVariableTypes().size();
            case MethodParametersAttribute mpa ->
                1 + 4 * mpa.parameters().size();
            case ModuleAttribute ma ->
                16 + subSize(ma.exports(), ModuleExportInfo::exportsTo, 6, 2)
                   + subSize(ma.opens(), ModuleOpenInfo::opensTo, 6, 2)
                   + subSize(ma.provides(), ModuleProvideInfo::providesWith, 4, 2)
                   + 6 * ma.requires().size()
                   + 2 * ma.uses().size();
            case ModuleHashesAttribute mha ->
                2 + moduleHashesSize(mha.hashes());
            case ModuleMainClassAttribute mmca -> {
                mmca.mainClass();
                yield 2;
            }
            case ModulePackagesAttribute mpa ->
                2 + 2 * mpa.packages().size();
            case ModuleResolutionAttribute mra ->
                2;
            case ModuleTargetAttribute mta -> {
                mta.targetPlatform();
                yield 2;
            }
            case NestHostAttribute nha -> {
                nha.nestHost();
                yield 2;
            }
            case NestMembersAttribute nma -> {
                if (ae.findAttribute(Attributes.nestHost()).isPresent()) {
                    errors.add(new VerifyError("Conflicting NestHost and NestMembers attributes in %s".formatted(toString(ae))));
                }
                yield 2 + 2 * nma.nestMembers().size();
            }
            case PermittedSubclassesAttribute psa -> {
                if (classModel.flags().has(AccessFlag.FINAL)) {
                    errors.add(new VerifyError("PermittedSubclasses attribute in final %s".formatted(toString(ae))));
                }
                yield 2 + 2 * psa.permittedSubclasses().size();
            }
            case RecordAttribute ra ->
                componentsSize(ra.components());
            case RuntimeVisibleAnnotationsAttribute aa ->
                annotationsSize(aa.annotations());
            case RuntimeInvisibleAnnotationsAttribute aa ->
                annotationsSize(aa.annotations());
            case RuntimeVisibleTypeAnnotationsAttribute aa ->
                typeAnnotationsSize(aa.annotations());
            case RuntimeInvisibleTypeAnnotationsAttribute aa ->
                typeAnnotationsSize(aa.annotations());
            case RuntimeVisibleParameterAnnotationsAttribute aa ->
                parameterAnnotationsSize(aa.parameterAnnotations());
            case RuntimeInvisibleParameterAnnotationsAttribute aa ->
                parameterAnnotationsSize(aa.parameterAnnotations());
            case SignatureAttribute sa -> {
                sa.signature();
                yield 2;
            }
            case SourceDebugExtensionAttribute sda ->
                sda.contents().length;
            case SourceFileAttribute sfa -> {
                sfa.sourceFile();
                yield 2;
            }
            case SourceIDAttribute sida -> {
                sida.sourceId();
                yield 2;
            }
            case StackMapTableAttribute smta ->
                2 + subSize(smta.entries(), frame -> stackMapFrameSize(frame));
            case SyntheticAttribute _ ->
                0;
            case UnknownAttribute _ ->
                -1;
            case CustomAttribute<?> _ ->
                -1;
            default -> // should not happen if all known attributes are verified
                throw new AssertionError(a);
        };
        if (size >= 0 && size != ((BoundAttribute)a).payloadLen()) {
            errors.add(new VerifyError("Wrong %s attribute length in %s".formatted(a.attributeName().stringValue(), toString(ae))));
        }
    }

    private static <T, S extends Collection<?>> int subSize(Collection<T> entries, Function<T, S> subMH, int entrySize, int subSize) {
        return subSize(entries, (ToIntFunction<T>) t -> entrySize + subSize * subMH.apply(t).size());
    }

    private static <T> int subSize(Collection<T> entries, ToIntFunction<T> subMH) {
        int l = 0;
        for (T entry : entries) {
            l += subMH.applyAsInt(entry);
        }
        return l;
    }

    private static int componentsSize(List<RecordComponentInfo> comps) {
        int l = 2;
        for (var rc : comps) {
            l += 4 + attributesSize(rc.attributes());
        }
        return l;
    }

    private static int attributesSize(List<Attribute<?>> attrs) {
        int l = 2;
        for (var a : attrs) {
            l += 6 + ((BoundAttribute)a).payloadLen();
        }
        return l;
    }

    private static int parameterAnnotationsSize(List<List<Annotation>> pans) {
        int l = 1;
        for (var ans : pans) {
            l += annotationsSize(ans);
        }
        return l;
    }

    private static int annotationsSize(List<Annotation> ans) {
        int l = 2;
        for (var an : ans) {
            l += annotationSize(an);
        }
        return l;
    }

    private static int typeAnnotationsSize(List<TypeAnnotation> ans) {
        int l = 2;
        for (var an : ans) {
            l += 2 + an.targetInfo().size() + 2 * an.targetPath().size() + annotationSize(an.annotation());
        }
        return l;
    }

    private static int annotationSize(Annotation an) {
        int l = 4;
        for (var el : an.elements()) {
            l += 2 + valueSize(el.value());
        }
        return l;
    }

    private static int valueSize(AnnotationValue val) {
        return 1 + switch (val) {
            case AnnotationValue.OfAnnotation oan ->
                annotationSize(oan.annotation());
            case AnnotationValue.OfArray oar -> {
                int l = 2;
                for (var v : oar.values()) {
                    l += valueSize(v);
                }
                yield l;
            }
            case AnnotationValue.OfConstant _, AnnotationValue.OfClass _ -> 2;
            case AnnotationValue.OfEnum _ -> 4;
        };
    }

    private static int moduleHashesSize(List<ModuleHashInfo> hashes) {
        int l = 2;
        for (var h : hashes) {
            h.moduleName();
            l += 4 + h.hash().length;
        }
        return l;
    }

    private int stackMapFrameSize(StackMapFrameInfo frame) {
        int ft = frame.frameType();
        if (ft <= SAME_FRAME_END) return 1;
        if (ft <= SAME_LOCALS_1_STACK_ITEM_FRAME_END) return 1 + verificationTypeSize(frame.stack().getFirst());
        if (ft > RESERVED_END) {
            if (ft == SAME_LOCALS_1_STACK_ITEM_EXTENDED) return 3 + verificationTypeSize(frame.stack().getFirst());
            if (ft <= SAME_FRAME_EXTENDED) return 3;
            if (ft <= APPEND_FRAME_END) {
                var loc = frame.locals();
                int l = 3;
                var k = ft - APPEND_FRAME_START + 1;
                for (int i = loc.size() - k; i < loc.size(); i++) {
                    l += verificationTypeSize(loc.get(i));
                }
                return l;
            }
            if (ft == FULL_FRAME) {
                int l = 7;
                for (var vt : frame.stack()) {
                    l += verificationTypeSize(vt);
                }
                for (var vt : frame.locals()) {
                    l += verificationTypeSize(vt);
                }
                return l;
            }
        }
        throw new IllegalArgumentException("Invalid stack map frame type " + ft);
    }

    private static int verificationTypeSize(StackMapFrameInfo.VerificationTypeInfo vti) {
        return switch (vti) {
            case StackMapFrameInfo.SimpleVerificationTypeInfo _ -> 1;
            case StackMapFrameInfo.ObjectVerificationTypeInfo ovti -> {
                ovti.classSymbol();
                yield 3;
            }
            case StackMapFrameInfo.UninitializedVerificationTypeInfo _ -> 3;
        };
    }

    private String className() {
        return classModel.thisClass().asSymbol().displayName();
    }

    private String toString(AttributedElement ae) {
        return switch (ae) {
            case CodeModel m -> "Code attribute for " + toString(m.parent().get());
            case FieldModel m -> "field %s.%s".formatted(
                    className(),
                    m.fieldName().stringValue());
            case MethodModel m -> "method %s::%s(%s)".formatted(
                    className(),
                    m.methodName().stringValue(),
                    m.methodTypeSymbol().parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(",")));
            case RecordComponentInfo i -> "Record component %s of class %s".formatted(
                    i.name().stringValue(),
                    className());
            default -> "class " + className();
        };
    }
}