001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.codegen;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.kotlin.codegen.annotation.WrappedAnnotated;
022    import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
023    import org.jetbrains.kotlin.descriptors.*;
024    import org.jetbrains.kotlin.descriptors.annotations.*;
025    import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
026    import org.jetbrains.kotlin.load.java.JvmAnnotationNames;
027    import org.jetbrains.kotlin.name.FqName;
028    import org.jetbrains.kotlin.resolve.AnnotationChecker;
029    import org.jetbrains.kotlin.resolve.constants.*;
030    import org.jetbrains.kotlin.resolve.constants.StringValue;
031    import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
032    import org.jetbrains.kotlin.types.*;
033    import org.jetbrains.org.objectweb.asm.*;
034    
035    import java.lang.annotation.*;
036    import java.util.*;
037    
038    public abstract class AnnotationCodegen {
039    
040        public static final class JvmFlagAnnotation {
041            private final FqName fqName;
042            private final int jvmFlag;
043    
044            public JvmFlagAnnotation(@NotNull String fqName, int jvmFlag) {
045                this.fqName = new FqName(fqName);
046                this.jvmFlag = jvmFlag;
047            }
048    
049            public boolean hasAnnotation(@NotNull Annotated annotated) {
050                return Annotations.Companion.findAnyAnnotation(annotated.getAnnotations(), fqName) != null;
051            }
052    
053            public int getJvmFlag() {
054                return jvmFlag;
055            }
056        }
057    
058        public static final List<JvmFlagAnnotation> FIELD_FLAGS = Arrays.asList(
059                new JvmFlagAnnotation("kotlin.jvm.Volatile", Opcodes.ACC_VOLATILE),
060                new JvmFlagAnnotation("kotlin.jvm.Transient", Opcodes.ACC_TRANSIENT)
061        );
062    
063        public static final List<JvmFlagAnnotation> METHOD_FLAGS = Arrays.asList(
064                new JvmFlagAnnotation("kotlin.jvm.Strictfp", Opcodes.ACC_STRICT),
065                new JvmFlagAnnotation("kotlin.jvm.Synchronized", Opcodes.ACC_SYNCHRONIZED)
066        );
067    
068        private static final AnnotationVisitor NO_ANNOTATION_VISITOR = new AnnotationVisitor(Opcodes.ASM5) {};
069    
070        private final KotlinTypeMapper typeMapper;
071    
072        private AnnotationCodegen(KotlinTypeMapper mapper) {
073            typeMapper = mapper;
074        }
075    
076        /**
077         * @param returnType can be null if not applicable (e.g. {@code annotated} is a class)
078         */
079        public void genAnnotations(@Nullable Annotated annotated, @Nullable Type returnType) {
080            genAnnotations(annotated, returnType, null);
081        }
082    
083        public void genAnnotations(@Nullable Annotated annotated, @Nullable Type returnType, @Nullable AnnotationUseSiteTarget allowedTarget) {
084            if (annotated == null) {
085                return;
086            }
087    
088            Set<String> annotationDescriptorsAlreadyPresent = new HashSet<String>();
089    
090            Annotations annotations = annotated.getAnnotations();
091    
092            for (AnnotationWithTarget annotationWithTarget : annotations.getAllAnnotations()) {
093                AnnotationDescriptor annotation = annotationWithTarget.getAnnotation();
094                AnnotationUseSiteTarget annotationTarget = annotationWithTarget.getTarget();
095    
096                // Skip targeted annotations by default
097                if (allowedTarget == null && annotationTarget != null) continue;
098    
099                // Skip if the target is not the same
100                if (allowedTarget != null && annotationTarget != null && allowedTarget != annotationTarget) continue;
101    
102                Set<KotlinTarget> applicableTargets = AnnotationChecker.applicableTargetSet(annotation);
103                if (annotated instanceof AnonymousFunctionDescriptor
104                    && !applicableTargets.contains(KotlinTarget.FUNCTION)
105                    && !applicableTargets.contains(KotlinTarget.PROPERTY_GETTER)
106                    && !applicableTargets.contains(KotlinTarget.PROPERTY_SETTER)) {
107                    assert (applicableTargets.contains(KotlinTarget.EXPRESSION)) :
108                            "Inconsistent target list for lambda annotation: " + applicableTargets + " on " + annotated;
109                    continue;
110                }
111                if (annotated instanceof ClassDescriptor
112                    && !applicableTargets.contains(KotlinTarget.CLASS)
113                    && !applicableTargets.contains(KotlinTarget.ANNOTATION_CLASS)) {
114                    ClassDescriptor classDescriptor = (ClassDescriptor) annotated;
115                    if (classDescriptor.getVisibility() == Visibilities.LOCAL) {
116                        assert applicableTargets.contains(KotlinTarget.EXPRESSION) :
117                                "Inconsistent target list for object literal annotation: " + applicableTargets + " on " + annotated;
118                        continue;
119                    }
120                }
121    
122                String descriptor = genAnnotation(annotation);
123                if (descriptor != null) {
124                    annotationDescriptorsAlreadyPresent.add(descriptor);
125                }
126            }
127    
128            generateAdditionalAnnotations(annotated, returnType, annotationDescriptorsAlreadyPresent);
129        }
130    
131        private void generateAdditionalAnnotations(
132                @NotNull Annotated annotated,
133                @Nullable Type returnType,
134                @NotNull Set<String> annotationDescriptorsAlreadyPresent
135        ) {
136            Annotated unwrapped = annotated;
137            if (annotated instanceof WrappedAnnotated) {
138                unwrapped = ((WrappedAnnotated) annotated).getOriginalAnnotated();
139            }
140    
141            if (unwrapped instanceof CallableDescriptor) {
142                CallableDescriptor descriptor = (CallableDescriptor) unwrapped;
143    
144                // No need to annotate privates, synthetic accessors and their parameters
145                if (isInvisibleFromTheOutside(descriptor)) return;
146                if (descriptor instanceof ValueParameterDescriptor && isInvisibleFromTheOutside(descriptor.getContainingDeclaration())) return;
147    
148                if (returnType != null && !AsmUtil.isPrimitive(returnType)) {
149                    generateNullabilityAnnotation(descriptor.getReturnType(), annotationDescriptorsAlreadyPresent);
150                }
151            }
152            if (unwrapped instanceof ClassDescriptor) {
153                ClassDescriptor classDescriptor = (ClassDescriptor) unwrapped;
154                if (classDescriptor.getKind() == ClassKind.ANNOTATION_CLASS) {
155                    generateDocumentedAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent);
156                    generateRetentionAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent);
157                    generateTargetAnnotation(classDescriptor, annotationDescriptorsAlreadyPresent);
158                }
159            }
160        }
161    
162        private static boolean isInvisibleFromTheOutside(@Nullable DeclarationDescriptor descriptor) {
163            if (descriptor instanceof CallableMemberDescriptor && KotlinTypeMapper.isAccessor((CallableMemberDescriptor) descriptor)) return false;
164            if (descriptor instanceof MemberDescriptor) {
165                return AsmUtil.getVisibilityAccessFlag((MemberDescriptor) descriptor) == Opcodes.ACC_PRIVATE;
166            }
167            return false;
168        }
169    
170        private void generateNullabilityAnnotation(@Nullable KotlinType type, @NotNull Set<String> annotationDescriptorsAlreadyPresent) {
171            if (type == null) return;
172    
173            if (isBareTypeParameterWithNullableUpperBound(type)) {
174                // This is to account for the case of, say
175                //   class Function<R> { fun invoke(): R }
176                // it would be a shame to put @Nullable on the return type of the function, and force all callers to check for null,
177                // so we put no annotations
178                return;
179            }
180    
181            if (FlexibleTypesKt.isFlexible(type)) {
182                // A flexible type whose lower bound in not-null and upper bound is nullable, should not be annotated
183                Flexibility flexibility = FlexibleTypesKt.flexibility(type);
184    
185                if (!TypeUtils.isNullableType(flexibility.getLowerBound()) && TypeUtils.isNullableType(flexibility.getUpperBound())) {
186                    AnnotationDescriptor notNull = type.getAnnotations().findAnnotation(JvmAnnotationNames.JETBRAINS_NOT_NULL_ANNOTATION);
187                    if (notNull != null) {
188                        generateAnnotationIfNotPresent(annotationDescriptorsAlreadyPresent, NotNull.class);
189                    }
190                    return;
191                }
192            }
193    
194            boolean isNullableType = TypeUtils.isNullableType(type);
195    
196            Class<?> annotationClass = isNullableType ? Nullable.class : NotNull.class;
197    
198            generateAnnotationIfNotPresent(annotationDescriptorsAlreadyPresent, annotationClass);
199        }
200    
201        private static final Map<KotlinTarget, ElementType> annotationTargetMap =
202                new EnumMap<KotlinTarget, ElementType>(KotlinTarget.class);
203    
204        static {
205            annotationTargetMap.put(KotlinTarget.CLASS, ElementType.TYPE);
206            annotationTargetMap.put(KotlinTarget.ANNOTATION_CLASS, ElementType.ANNOTATION_TYPE);
207            annotationTargetMap.put(KotlinTarget.CONSTRUCTOR, ElementType.CONSTRUCTOR);
208            annotationTargetMap.put(KotlinTarget.LOCAL_VARIABLE, ElementType.LOCAL_VARIABLE);
209            annotationTargetMap.put(KotlinTarget.FUNCTION, ElementType.METHOD);
210            annotationTargetMap.put(KotlinTarget.PROPERTY_GETTER, ElementType.METHOD);
211            annotationTargetMap.put(KotlinTarget.PROPERTY_SETTER, ElementType.METHOD);
212            annotationTargetMap.put(KotlinTarget.FIELD, ElementType.FIELD);
213            annotationTargetMap.put(KotlinTarget.VALUE_PARAMETER, ElementType.PARAMETER);
214        }
215    
216        private void generateTargetAnnotation(@NotNull ClassDescriptor classDescriptor, @NotNull Set<String> annotationDescriptorsAlreadyPresent) {
217            String descriptor = Type.getType(Target.class).getDescriptor();
218            if (!annotationDescriptorsAlreadyPresent.add(descriptor)) return;
219            Set<KotlinTarget> targets = AnnotationChecker.Companion.applicableTargetSet(classDescriptor);
220            Set<ElementType> javaTargets;
221            if (targets == null) {
222                javaTargets = getJavaTargetList(classDescriptor);
223                if (javaTargets == null) return;
224            }
225            else {
226                javaTargets = EnumSet.noneOf(ElementType.class);
227                for (KotlinTarget target : targets) {
228                    if (annotationTargetMap.get(target) == null) continue;
229                    javaTargets.add(annotationTargetMap.get(target));
230                }
231            }
232            AnnotationVisitor visitor = visitAnnotation(descriptor, true);
233            AnnotationVisitor arrayVisitor = visitor.visitArray("value");
234            for (ElementType javaTarget : javaTargets) {
235                arrayVisitor.visitEnum(null, Type.getType(ElementType.class).getDescriptor(), javaTarget.name());
236            }
237            arrayVisitor.visitEnd();
238            visitor.visitEnd();
239        }
240    
241        private void generateRetentionAnnotation(@NotNull ClassDescriptor classDescriptor, @NotNull Set<String> annotationDescriptorsAlreadyPresent) {
242            RetentionPolicy policy = getRetentionPolicy(classDescriptor);
243            String descriptor = Type.getType(Retention.class).getDescriptor();
244            if (!annotationDescriptorsAlreadyPresent.add(descriptor)) return;
245            AnnotationVisitor visitor = visitAnnotation(descriptor, true);
246            visitor.visitEnum("value", Type.getType(RetentionPolicy.class).getDescriptor(), policy.name());
247            visitor.visitEnd();
248        }
249    
250        private void generateDocumentedAnnotation(@NotNull ClassDescriptor classDescriptor, @NotNull Set<String> annotationDescriptorsAlreadyPresent) {
251            boolean documented = DescriptorUtilsKt.isDocumentedAnnotation(classDescriptor);
252            if (!documented) return;
253            String descriptor = Type.getType(Documented.class).getDescriptor();
254            if (!annotationDescriptorsAlreadyPresent.add(descriptor)) return;
255            AnnotationVisitor visitor = visitAnnotation(descriptor, true);
256            visitor.visitEnd();
257        }
258    
259        private void generateAnnotationIfNotPresent(Set<String> annotationDescriptorsAlreadyPresent, Class<?> annotationClass) {
260            String descriptor = Type.getType(annotationClass).getDescriptor();
261            if (!annotationDescriptorsAlreadyPresent.contains(descriptor)) {
262                visitAnnotation(descriptor, false).visitEnd();
263            }
264        }
265    
266        private static boolean isBareTypeParameterWithNullableUpperBound(@NotNull KotlinType type) {
267            ClassifierDescriptor classifier = type.getConstructor().getDeclarationDescriptor();
268            return !type.isMarkedNullable() && classifier instanceof TypeParameterDescriptor && TypeUtils.hasNullableSuperType(type);
269        }
270    
271        public void generateAnnotationDefaultValue(@NotNull ConstantValue<?> value, @NotNull KotlinType expectedType) {
272            AnnotationVisitor visitor = visitAnnotation(null, false);  // Parameters are unimportant
273            genCompileTimeValue(null, value, visitor);
274            visitor.visitEnd();
275        }
276    
277        @Nullable
278        private String genAnnotation(@NotNull AnnotationDescriptor annotationDescriptor) {
279            ClassifierDescriptor classifierDescriptor = annotationDescriptor.getType().getConstructor().getDeclarationDescriptor();
280            assert classifierDescriptor != null : "Annotation descriptor has no class: " + annotationDescriptor;
281            RetentionPolicy rp = getRetentionPolicy(classifierDescriptor);
282            if (rp == RetentionPolicy.SOURCE && typeMapper.getClassBuilderMode() == ClassBuilderMode.FULL) {
283                return null;
284            }
285    
286            String descriptor = typeMapper.mapType(annotationDescriptor.getType()).getDescriptor();
287            AnnotationVisitor annotationVisitor = visitAnnotation(descriptor, rp == RetentionPolicy.RUNTIME);
288    
289            genAnnotationArguments(annotationDescriptor, annotationVisitor);
290            annotationVisitor.visitEnd();
291    
292            return descriptor;
293        }
294    
295        private void genAnnotationArguments(AnnotationDescriptor annotationDescriptor, AnnotationVisitor annotationVisitor) {
296            for (Map.Entry<ValueParameterDescriptor, ConstantValue<?>> entry : annotationDescriptor.getAllValueArguments().entrySet()) {
297                ValueParameterDescriptor descriptor = entry.getKey();
298                String name = descriptor.getName().asString();
299                genCompileTimeValue(name, entry.getValue(), annotationVisitor);
300            }
301        }
302    
303        private void genCompileTimeValue(
304                @Nullable final String name,
305                @NotNull ConstantValue<?> value,
306                @NotNull final AnnotationVisitor annotationVisitor
307        ) {
308            AnnotationArgumentVisitor argumentVisitor = new AnnotationArgumentVisitor<Void, Void>() {
309                @Override
310                public Void visitLongValue(@NotNull LongValue value, Void data) {
311                    return visitSimpleValue(value);
312                }
313    
314                @Override
315                public Void visitIntValue(IntValue value, Void data) {
316                    return visitSimpleValue(value);
317                }
318    
319                @Override
320                public Void visitShortValue(ShortValue value, Void data) {
321                    return visitSimpleValue(value);
322                }
323    
324                @Override
325                public Void visitByteValue(ByteValue value, Void data) {
326                    return visitSimpleValue(value);
327                }
328    
329                @Override
330                public Void visitDoubleValue(DoubleValue value, Void data) {
331                    return visitSimpleValue(value);
332                }
333    
334                @Override
335                public Void visitFloatValue(FloatValue value, Void data) {
336                    return visitSimpleValue(value);
337                }
338    
339                @Override
340                public Void visitBooleanValue(BooleanValue value, Void data) {
341                    return visitSimpleValue(value);
342                }
343    
344                @Override
345                public Void visitCharValue(CharValue value, Void data) {
346                    return visitSimpleValue(value);
347                }
348    
349                @Override
350                public Void visitStringValue(StringValue value, Void data) {
351                    return visitSimpleValue(value);
352                }
353    
354                @Override
355                public Void visitEnumValue(EnumValue value, Void data) {
356                    String propertyName = value.getValue().getName().asString();
357                    annotationVisitor.visitEnum(name, typeMapper.mapType(value.getType()).getDescriptor(), propertyName);
358                    return null;
359                }
360    
361                @Override
362                public Void visitArrayValue(ArrayValue value, Void data) {
363                    AnnotationVisitor visitor = annotationVisitor.visitArray(name);
364                    for (ConstantValue<?> argument : value.getValue()) {
365                        genCompileTimeValue(null, argument, visitor);
366                    }
367                    visitor.visitEnd();
368                    return null;
369                }
370    
371                @Override
372                public Void visitAnnotationValue(AnnotationValue value, Void data) {
373                    String internalAnnName = typeMapper.mapType(value.getValue().getType()).getDescriptor();
374                    AnnotationVisitor visitor = annotationVisitor.visitAnnotation(name, internalAnnName);
375                    genAnnotationArguments(value.getValue(), visitor);
376                    visitor.visitEnd();
377                    return null;
378                }
379    
380                @Override
381                public Void visitKClassValue(KClassValue value, Void data) {
382                    annotationVisitor.visit(name, typeMapper.mapType(value.getValue()));
383                    return null;
384                }
385    
386                private Void visitSimpleValue(ConstantValue<?> value) {
387                    annotationVisitor.visit(name, value.getValue());
388                    return null;
389                }
390    
391                @Override
392                public Void visitErrorValue(ErrorValue value, Void data) {
393                    return visitUnsupportedValue(value);
394                }
395    
396                @Override
397                public Void visitNullValue(NullValue value, Void data) {
398                    return visitUnsupportedValue(value);
399                }
400    
401                private Void visitUnsupportedValue(ConstantValue<?> value) {
402                    ClassBuilderMode mode = typeMapper.getClassBuilderMode();
403                    switch (mode) {
404                        case FULL:
405                            throw new IllegalStateException("Don't know how to compile annotation value " + value);
406                        case LIGHT_CLASSES:
407                        case KAPT:
408                            return null;
409                        default:
410                            throw new IllegalStateException("Unknown builder mode: " + mode);
411                    }
412                }
413            };
414    
415            value.accept(argumentVisitor, null);
416        }
417    
418        private static final Map<KotlinRetention, RetentionPolicy> annotationRetentionMap =
419                new EnumMap<KotlinRetention, RetentionPolicy>(KotlinRetention.class);
420    
421        static {
422            annotationRetentionMap.put(KotlinRetention.SOURCE, RetentionPolicy.SOURCE);
423            annotationRetentionMap.put(KotlinRetention.BINARY, RetentionPolicy.CLASS);
424            annotationRetentionMap.put(KotlinRetention.RUNTIME, RetentionPolicy.RUNTIME);
425        }
426    
427        @Nullable
428        private Set<ElementType> getJavaTargetList(ClassDescriptor descriptor) {
429            AnnotationDescriptor targetAnnotation = descriptor.getAnnotations().findAnnotation(new FqName(Target.class.getName()));
430            if (targetAnnotation != null) {
431                Collection<ConstantValue<?>> valueArguments = targetAnnotation.getAllValueArguments().values();
432                if (!valueArguments.isEmpty()) {
433                    ConstantValue<?> compileTimeConstant = valueArguments.iterator().next();
434                    if (compileTimeConstant instanceof ArrayValue) {
435                        List<? extends ConstantValue<?>> values = ((ArrayValue) compileTimeConstant).getValue();
436                        Set<ElementType> result = EnumSet.noneOf(ElementType.class);
437                        for (ConstantValue<?> value : values) {
438                            if (value instanceof EnumValue) {
439                                ClassDescriptor enumEntry = ((EnumValue) value).getValue();
440                                KotlinType classObjectType = DescriptorUtilsKt.getClassValueType(enumEntry);
441                                if (classObjectType != null) {
442                                    if ("java/lang/annotation/ElementType".equals(typeMapper.mapType(classObjectType).getInternalName())) {
443                                        result.add(ElementType.valueOf(enumEntry.getName().asString()));
444                                    }
445                                }
446                            }
447                        }
448                        return result;
449                    }
450                }
451            }
452            return null;
453        }
454    
455        @NotNull
456        private RetentionPolicy getRetentionPolicy(@NotNull Annotated descriptor) {
457            KotlinRetention retention = DescriptorUtilsKt.getAnnotationRetention(descriptor);
458            if (retention != null) {
459                return annotationRetentionMap.get(retention);
460            }
461            AnnotationDescriptor retentionAnnotation = descriptor.getAnnotations().findAnnotation(new FqName(Retention.class.getName()));
462            if (retentionAnnotation != null) {
463                Collection<ConstantValue<?>> valueArguments = retentionAnnotation.getAllValueArguments().values();
464                if (!valueArguments.isEmpty()) {
465                    ConstantValue<?> compileTimeConstant = valueArguments.iterator().next();
466                    if (compileTimeConstant instanceof EnumValue) {
467                        ClassDescriptor enumEntry = ((EnumValue) compileTimeConstant).getValue();
468                        KotlinType classObjectType = DescriptorUtilsKt.getClassValueType(enumEntry);
469                        if (classObjectType != null) {
470                            if ("java/lang/annotation/RetentionPolicy".equals(typeMapper.mapType(classObjectType).getInternalName())) {
471                                return RetentionPolicy.valueOf(enumEntry.getName().asString());
472                            }
473                        }
474                    }
475                }
476            }
477    
478            return RetentionPolicy.RUNTIME;
479        }
480    
481        @NotNull
482        abstract AnnotationVisitor visitAnnotation(String descr, boolean visible);
483    
484        public static AnnotationCodegen forClass(final ClassVisitor cv, KotlinTypeMapper mapper) {
485            return new AnnotationCodegen(mapper) {
486                @NotNull
487                @Override
488                AnnotationVisitor visitAnnotation(String descr, boolean visible) {
489                    return safe(cv.visitAnnotation(descr, visible));
490                }
491            };
492        }
493    
494        public static AnnotationCodegen forMethod(final MethodVisitor mv, KotlinTypeMapper mapper) {
495            return new AnnotationCodegen(mapper) {
496                @NotNull
497                @Override
498                AnnotationVisitor visitAnnotation(String descr, boolean visible) {
499                    return safe(mv.visitAnnotation(descr, visible));
500                }
501            };
502        }
503    
504        public static AnnotationCodegen forField(final FieldVisitor fv, KotlinTypeMapper mapper) {
505            return new AnnotationCodegen(mapper) {
506                @NotNull
507                @Override
508                AnnotationVisitor visitAnnotation(String descr, boolean visible) {
509                    return safe(fv.visitAnnotation(descr, visible));
510                }
511            };
512        }
513    
514        public static AnnotationCodegen forParameter(final int parameter, final MethodVisitor mv, KotlinTypeMapper mapper) {
515            return new AnnotationCodegen(mapper) {
516                @NotNull
517                @Override
518                AnnotationVisitor visitAnnotation(String descr, boolean visible) {
519                    return safe(mv.visitParameterAnnotation(parameter, descr, visible));
520                }
521            };
522        }
523    
524        public static AnnotationCodegen forAnnotationDefaultValue(final MethodVisitor mv, KotlinTypeMapper mapper) {
525            return new AnnotationCodegen(mapper) {
526                @NotNull
527                @Override
528                AnnotationVisitor visitAnnotation(String descr, boolean visible) {
529                    return safe(mv.visitAnnotationDefault());
530                }
531            };
532        }
533    
534        @NotNull
535        private static AnnotationVisitor safe(@Nullable AnnotationVisitor av) {
536            return av == null ? NO_ANNOTATION_VISITOR : av;
537        }
538    }