ART: Fixes for constructor parameter annotations

Synthesize empty parameter annotations for implicit parameters on
constructors. Reflective methods for recovering parameter annotations
expect them to be present though they may not be present in the DEX file.

Bug: b/68033708
Test: art/test/run-test --host 715

Change-Id: I0827c7e71ff7c7e044fc9dd6c5aac639a0e1a4c6
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 145eb67..9276994 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -295,6 +295,11 @@
   return GetDexFile()->GetClassDef(GetClassDefIndex());
 }
 
+inline size_t ArtMethod::GetNumberOfParameters() {
+  constexpr size_t return_type_count = 1u;
+  return strlen(GetShorty()) - return_type_count;
+}
+
 inline const char* ArtMethod::GetReturnTypeDescriptor() {
   DCHECK(!IsProxyMethod());
   const DexFile* dex_file = GetDexFile();
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 013856f..5d9b729 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -602,6 +602,8 @@
 
   const DexFile::ClassDef& GetClassDef() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  ALWAYS_INLINE size_t GetNumberOfParameters() REQUIRES_SHARED(Locks::mutator_lock_);
+
   const char* GetReturnTypeDescriptor() REQUIRES_SHARED(Locks::mutator_lock_);
 
   ALWAYS_INLINE Primitive::Type GetReturnTypePrimitive() REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/dex/dex_file_annotations.cc b/runtime/dex/dex_file_annotations.cc
index 3431bb7..6f3354b 100644
--- a/runtime/dex/dex_file_annotations.cc
+++ b/runtime/dex/dex_file_annotations.cc
@@ -1121,6 +1121,21 @@
   return ProcessAnnotationSetRefList(ClassData(method), set_ref_list, size);
 }
 
+uint32_t GetNumberOfAnnotatedMethodParameters(ArtMethod* method) {
+  const DexFile* dex_file = method->GetDexFile();
+  const DexFile::ParameterAnnotationsItem* parameter_annotations =
+      FindAnnotationsItemForMethod(method);
+  if (parameter_annotations == nullptr) {
+    return 0u;
+  }
+  const DexFile::AnnotationSetRefList* set_ref_list =
+      dex_file->GetParameterAnnotationSetRefList(parameter_annotations);
+  if (set_ref_list == nullptr) {
+    return 0u;
+  }
+  return set_ref_list->size_;
+}
+
 mirror::Object* GetAnnotationForMethodParameter(ArtMethod* method,
                                                 uint32_t parameter_idx,
                                                 Handle<mirror::Class> annotation_class) {
@@ -1141,7 +1156,9 @@
   const DexFile::AnnotationSetRefItem* annotation_set_ref = &set_ref_list->list_[parameter_idx];
   const DexFile::AnnotationSetItem* annotation_set =
      dex_file->GetSetRefItemItem(annotation_set_ref);
-
+  if (annotation_set == nullptr) {
+    return nullptr;
+  }
   return GetAnnotationObjectFromAnnotationSet(ClassData(method),
                                               annotation_set,
                                               DexFile::kDexVisibilityRuntime,
diff --git a/runtime/dex/dex_file_annotations.h b/runtime/dex/dex_file_annotations.h
index d7ebf84..4bb0d75 100644
--- a/runtime/dex/dex_file_annotations.h
+++ b/runtime/dex/dex_file_annotations.h
@@ -55,6 +55,8 @@
     REQUIRES_SHARED(Locks::mutator_lock_);
 mirror::ObjectArray<mirror::Object>* GetParameterAnnotations(ArtMethod* method)
     REQUIRES_SHARED(Locks::mutator_lock_);
+uint32_t GetNumberOfAnnotatedMethodParameters(ArtMethod* method)
+    REQUIRES_SHARED(Locks::mutator_lock_);
 mirror::Object* GetAnnotationForMethodParameter(ArtMethod* method,
                                                 uint32_t parameter_idx,
                                                 Handle<mirror::Class> annotation_class)
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index a1d0ff7..6000317 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -186,6 +186,11 @@
 
   void SetAccessFlags(uint32_t new_access_flags) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Returns true if the class is an enum.
+  ALWAYS_INLINE bool IsEnum() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return (GetAccessFlags() & kAccEnum) != 0;
+  }
+
   // Returns true if the class is an interface.
   ALWAYS_INLINE bool IsInterface() REQUIRES_SHARED(Locks::mutator_lock_) {
     return (GetAccessFlags() & kAccInterface) != 0;
diff --git a/runtime/native/java_lang_reflect_Executable.cc b/runtime/native/java_lang_reflect_Executable.cc
index a5e70af..b129c66 100644
--- a/runtime/native/java_lang_reflect_Executable.cc
+++ b/runtime/native/java_lang_reflect_Executable.cc
@@ -70,7 +70,6 @@
   if (method->GetDeclaringClass()->IsProxyClass()) {
     return nullptr;
   }
-  StackHandleScope<1> hs(soa.Self());
   return soa.AddLocalReference<jobjectArray>(annotations::GetSignatureAnnotationForMethod(method));
 }
 
@@ -80,9 +79,76 @@
   ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod);
   if (method->IsProxyMethod()) {
     return nullptr;
-  } else {
-    return soa.AddLocalReference<jobjectArray>(annotations::GetParameterAnnotations(method));
   }
+
+  StackHandleScope<4> hs(soa.Self());
+  Handle<mirror::ObjectArray<mirror::Object>> annotations =
+      hs.NewHandle(annotations::GetParameterAnnotations(method));
+  if (annotations.IsNull()) {
+    return nullptr;
+  }
+
+  // If the method is not a constructor, or has parameter annotations
+  // for each parameter, then we can return those annotations
+  // unmodified. Otherwise, we need to look at whether the
+  // constructor has implicit parameters as these may need padding
+  // with empty parameter annotations.
+  if (!method->IsConstructor() ||
+      annotations->GetLength() == static_cast<int>(method->GetNumberOfParameters())) {
+    return soa.AddLocalReference<jobjectArray>(annotations.Get());
+  }
+
+  // If declaring class is a local or an enum, do not pad parameter
+  // annotations, as the implicit constructor parameters are an implementation
+  // detail rather than required by JLS.
+  Handle<mirror::Class> declaring_class = hs.NewHandle(method->GetDeclaringClass());
+  if (annotations::GetEnclosingMethod(declaring_class) != nullptr ||
+      declaring_class->IsEnum()) {
+    return soa.AddLocalReference<jobjectArray>(annotations.Get());
+  }
+
+  // Prepare to resize the annotations so there is 1:1 correspondence
+  // with the constructor parameters.
+  Handle<mirror::ObjectArray<mirror::Object>> resized_annotations = hs.NewHandle(
+      mirror::ObjectArray<mirror::Object>::Alloc(
+          soa.Self(),
+          annotations->GetClass(),
+          static_cast<int>(method->GetNumberOfParameters())));
+  if (resized_annotations.IsNull()) {
+    DCHECK(soa.Self()->IsExceptionPending());
+    return nullptr;
+  }
+
+  static constexpr bool kTransactionActive = false;
+  const int32_t offset = resized_annotations->GetLength() - annotations->GetLength();
+  if (offset > 0) {
+    // Workaround for dexers (d8/dx) that do not insert annotations
+    // for implicit parameters (b/68033708).
+    ObjPtr<mirror::Class> annotation_array_class =
+        soa.Decode<mirror::Class>(WellKnownClasses::java_lang_annotation_Annotation__array);
+    Handle<mirror::ObjectArray<mirror::Object>> empty_annotations = hs.NewHandle(
+        mirror::ObjectArray<mirror::Object>::Alloc(soa.Self(), annotation_array_class, 0));
+    if (empty_annotations.IsNull()) {
+      DCHECK(soa.Self()->IsExceptionPending());
+      return nullptr;
+    }
+    for (int i = 0; i < offset; ++i) {
+      resized_annotations->SetWithoutChecks<kTransactionActive>(i, empty_annotations.Get());
+    }
+    for (int i = 0; i < annotations->GetLength(); ++i) {
+      ObjPtr<mirror::Object> annotation = annotations->GetWithoutChecks(i);
+      resized_annotations->SetWithoutChecks<kTransactionActive>(i + offset, annotation);
+    }
+  } else {
+    // Workaround for Jack (defunct) erroneously inserting annotations
+    // for local classes (b/68033708).
+    DCHECK_LT(offset, 0);
+    for (int i = 0; i < resized_annotations->GetLength(); ++i) {
+      ObjPtr<mirror::Object> annotation = annotations->GetWithoutChecks(i - offset);
+      resized_annotations->SetWithoutChecks<kTransactionActive>(i, annotation);
+    }
+  }
+  return soa.AddLocalReference<jobjectArray>(resized_annotations.Get());
 }
 
 static jobjectArray Executable_getParameters0(JNIEnv* env, jobject javaMethod) {
diff --git a/runtime/native/java_lang_reflect_Parameter.cc b/runtime/native/java_lang_reflect_Parameter.cc
index 0b3015b..1ab9109 100644
--- a/runtime/native/java_lang_reflect_Parameter.cc
+++ b/runtime/native/java_lang_reflect_Parameter.cc
@@ -58,6 +58,40 @@
     return nullptr;
   }
 
+  uint32_t annotated_parameter_count = annotations::GetNumberOfAnnotatedMethodParameters(method);
+  if (annotated_parameter_count == 0u) {
+    return nullptr;
+  }
+
+  // For constructors with implicit arguments, we may need to adjust
+  // annotation positions based on whether the implicit parameters are
+  // expected to known and not just a compiler implementation detail.
+  if (method->IsConstructor()) {
+    StackHandleScope<1> hs(soa.Self());
+    // If declaring class is a local or an enum, do not pad parameter
+    // annotations, as the implicit constructor parameters are an
+    // implementation detail rather than required by JLS.
+    Handle<mirror::Class> declaring_class = hs.NewHandle(method->GetDeclaringClass());
+    if (annotations::GetEnclosingMethod(declaring_class) == nullptr && !declaring_class->IsEnum()) {
+      // Adjust the parameter index if the number of annotations does
+      // not match the number of parameters.
+      if (annotated_parameter_count <= parameter_count) {
+        // Workaround for dexer not inserting annotation state for implicit parameters (b/68033708).
+        uint32_t skip_count = parameter_count - annotated_parameter_count;
+        DCHECK_GE(2u, skip_count);
+        if (parameterIndex < static_cast<jint>(skip_count)) {
+          return nullptr;
+        }
+        parameterIndex -= skip_count;
+      } else {
+        // Workaround for Jack erroneously inserting implicit parameter for local classes
+        // (b/68033708).
+        DCHECK_EQ(1u, annotated_parameter_count - parameter_count);
+        parameterIndex += static_cast<jint>(annotated_parameter_count - parameter_count);
+      }
+    }
+  }
+
   StackHandleScope<1> hs(soa.Self());
   Handle<mirror::Class> klass(hs.NewHandle(soa.Decode<mirror::Class>(annotationType)));
   return soa.AddLocalReference<jobject>(
@@ -65,9 +99,10 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  FAST_NATIVE_METHOD(Parameter,
-                getAnnotationNative,
-                "(Ljava/lang/reflect/Executable;ILjava/lang/Class;)Ljava/lang/annotation/Annotation;"),
+  FAST_NATIVE_METHOD(
+      Parameter,
+      getAnnotationNative,
+      "(Ljava/lang/reflect/Executable;ILjava/lang/Class;)Ljava/lang/annotation/Annotation;"),
 };
 
 void register_java_lang_reflect_Parameter(JNIEnv* env) {
diff --git a/test/715-clinit-implicit-parameter-annotations/build b/test/715-clinit-implicit-parameter-annotations/build
new file mode 100644
index 0000000..4753c8c
--- /dev/null
+++ b/test/715-clinit-implicit-parameter-annotations/build
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Make us exit on a failure
+set -e
+
+# Always use D8 as DX does not support propagating parameter name and
+# access_flag information.
+export USE_D8=true
+
+./default-build "$@" --experimental parameter-annotations
diff --git a/test/715-clinit-implicit-parameter-annotations/expected.txt b/test/715-clinit-implicit-parameter-annotations/expected.txt
new file mode 100644
index 0000000..357eb62
--- /dev/null
+++ b/test/715-clinit-implicit-parameter-annotations/expected.txt
@@ -0,0 +1,113 @@
+Main
+ public Main()
+Main$1LocalClassStaticContext
+ Main$1LocalClassStaticContext(int)
+  Parameter [0]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+Main$1LocalClassStaticContextWithCapture
+ Main$1LocalClassStaticContextWithCapture(java.lang.String,long)
+  Parameter [0]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+Main$1LocalClassStaticContextWithCaptureAlternateOrdering
+ Main$1LocalClassStaticContextWithCaptureAlternateOrdering(java.lang.String,long)
+  Parameter [0]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+Main$1LocalClass
+ Main$1LocalClass(Main,int)
+  Parameter [0]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+Main$1LocalClassWithCapture
+ Main$1LocalClassWithCapture(Main,java.lang.String,long)
+  Parameter [0]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+Main$Inner
+ Main$Inner(Main,int,java.lang.String)
+  Parameter [0]:    Main$AnnotationA No
+    Main$AnnotationB No
+  Parameter [1]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+  Parameter [2]:    Main$AnnotationA No
+    Main$AnnotationB No
+ Main$Inner(Main,int,java.lang.String,boolean)
+  Parameter [0]:    Main$AnnotationA No
+    Main$AnnotationB No
+  Parameter [1]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+  Parameter [2]:    Main$AnnotationA No
+    Main$AnnotationB No
+  Parameter [3]:    Indexed : @Main$AnnotationB(value=x)
+    Array : @Main$AnnotationB(value=x)
+    Main$AnnotationA No
+    Main$AnnotationB Yes
+    @Main$AnnotationB(value=x)
+Main$StaticInner
+ Main$StaticInner(int,java.lang.String)
+  Parameter [0]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+  Parameter [1]:    Main$AnnotationA No
+    Main$AnnotationB No
+ Main$StaticInner(int,java.lang.String,boolean)
+  Parameter [0]:    Indexed : @Main$AnnotationB(value=foo)
+    Array : @Main$AnnotationB(value=foo)
+    Main$AnnotationA No
+    Main$AnnotationB Yes
+    @Main$AnnotationB(value=foo)
+  Parameter [1]:    Main$AnnotationA No
+    Main$AnnotationB No
+  Parameter [2]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+Main$ImportantNumber
+ private Main$ImportantNumber(java.lang.String,int,double)
+  Parameter [0]:    Indexed : @Main$AnnotationA()
+    Array : @Main$AnnotationA()
+    Main$AnnotationA Yes
+    @Main$AnnotationA()
+    Main$AnnotationB No
+ private Main$ImportantNumber(java.lang.String,int,double,boolean)
+  Parameter [0]:    Indexed : @Main$AnnotationB(value=x)
+    Array : @Main$AnnotationB(value=x)
+    Main$AnnotationA No
+    Main$AnnotationB Yes
+    @Main$AnnotationB(value=x)
+  Parameter [1]:    Indexed : @Main$AnnotationB(value=y)
+    Array : @Main$AnnotationB(value=y)
+    Main$AnnotationA No
+    Main$AnnotationB Yes
+    @Main$AnnotationB(value=y)
+Main$BinaryNumber
+ private Main$BinaryNumber(java.lang.String,int)
+  Parameter [0]:    Main$AnnotationA No
+    Main$AnnotationB No
+  Parameter [1]:    Main$AnnotationA No
+    Main$AnnotationB No
+Main$1
+ Main$1(java.lang.String)
+  Parameter [0]:    Main$AnnotationA No
+    Main$AnnotationB No
diff --git a/test/715-clinit-implicit-parameter-annotations/info.txt b/test/715-clinit-implicit-parameter-annotations/info.txt
new file mode 100644
index 0000000..31afd62
--- /dev/null
+++ b/test/715-clinit-implicit-parameter-annotations/info.txt
@@ -0,0 +1,5 @@
+Tests ART synthesizes parameter annotations for implicit parameters on
+constructors. Inner class and enum constructors may have implicit
+parameters. If the constructor has parameter annotations, the implicit
+parameters may not have annotations in the DEX file, but code that
+looks at these annotations will expect them to.
diff --git a/test/715-clinit-implicit-parameter-annotations/src/Main.java b/test/715-clinit-implicit-parameter-annotations/src/Main.java
new file mode 100644
index 0000000..351e3a9
--- /dev/null
+++ b/test/715-clinit-implicit-parameter-annotations/src/Main.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Parameter;
+
+public class Main {
+    // A simple parameter annotation
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface AnnotationA {}
+
+    // A parameter annotation with additional state
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface AnnotationB {
+        String value() default "default-value";
+    }
+
+    // An inner class whose constructors with have an implicit
+    // argument for the enclosing instance.
+    public class Inner {
+        private final int number;
+        private final String text;
+        boolean flag;
+
+        Inner(@AnnotationA int number, String text) {
+            this.number = number;
+            this.text = text;
+            this.flag = false;
+        }
+
+        Inner(@AnnotationA int number, String text, @AnnotationB("x") boolean flag) {
+            this.number = number;
+            this.text = text;
+            this.flag = flag;
+        }
+    }
+
+    // An inner class whose constructors with have no implicit
+    // arguments for the enclosing instance.
+    public static class StaticInner {
+        private final int number;
+        private final String text;
+        boolean flag;
+
+        StaticInner(@AnnotationA int number, String text) {
+            this.number = number;
+            this.text = text;
+            this.flag = false;
+        }
+
+        StaticInner(@AnnotationB("foo") int number, String text, @AnnotationA boolean flag) {
+            this.number = number;
+            this.text = text;
+            this.flag = flag;
+        }
+    }
+
+    public enum ImportantNumber {
+        ONE(1.0),
+        TWO(2.0),
+        MANY(3.0, true);
+
+        private double doubleValue;
+        private boolean isLarge;
+
+        ImportantNumber(@AnnotationA double doubleValue) {
+            this.doubleValue = doubleValue;
+            this.isLarge = false;
+        }
+
+        ImportantNumber(@AnnotationB("x") double doubleValue, @AnnotationB("y") boolean isLarge) {
+            this.doubleValue = doubleValue;
+            this.isLarge = isLarge;
+        }
+    }
+
+    public enum BinaryNumber {
+        ZERO,
+        ONE;
+    }
+
+    private abstract static class AnonymousBase {
+        public AnonymousBase(@AnnotationA String s) {}
+    }
+
+    private static String annotationToNormalizedString(Annotation annotation) {
+        // String.replace() to accomodate different representation across VMs.
+        return annotation.toString().replace("\"", "");
+    }
+
+    private static void DumpConstructorParameterAnnotations(Class<?> cls) throws Throwable {
+        System.out.println(cls.getName());
+        for (Constructor c : cls.getDeclaredConstructors()) {
+            System.out.println(" " + c);
+            Annotation[][] annotations = c.getParameterAnnotations();
+            Parameter[] parameters = c.getParameters();
+            for (int i = 0; i < annotations.length; ++i) {
+                // Exercise java.lang.reflect.Executable.getParameterAnnotationsNative()
+                // which retrieves all annotations for the parameters.
+                System.out.print("  Parameter [" + i + "]:");
+                for (Annotation annotation : parameters[i].getAnnotations()) {
+                    System.out.println("    Indexed : " + annotationToNormalizedString(annotation));
+                }
+                for (Annotation annotation : annotations[i]) {
+                    System.out.println("    Array : " + annotationToNormalizedString(annotation));
+                }
+
+                // Exercise Parameter.getAnnotationNative() with
+                // retrieves a single parameter annotation according to type.
+                Object[] opaqueClasses = new Object[] {AnnotationA.class, AnnotationB.class};
+                for (Object opaqueClass : opaqueClasses) {
+                    @SuppressWarnings("unchecked")
+                    Class<? extends Annotation> annotationClass =
+                            (Class<? extends Annotation>) opaqueClass;
+                    Annotation annotation = parameters[i].getDeclaredAnnotation(annotationClass);
+                    String hasAnnotation = (annotation != null ? "Yes" : "No");
+                    System.out.println("    " + annotationClass.getName() + " " + hasAnnotation);
+
+                    Annotation[] parameterAnnotations = parameters[i].getDeclaredAnnotationsByType(annotationClass);
+                    for (Annotation parameterAnnotation : parameterAnnotations) {
+                        System.out.println("    " + annotationToNormalizedString(parameterAnnotation));
+                    }
+                }
+            }
+        }
+    }
+
+    private Class<?> getLocalClassWithEnclosingInstanceCapture() {
+        class LocalClass {
+            private final int integerValue;
+
+            LocalClass(@AnnotationA int integerValue) {
+                this.integerValue = integerValue;
+            }
+        }
+        return LocalClass.class;
+    }
+
+    private Class<?> getLocalClassWithEnclosingInstanceAndLocalCapture() {
+        final long CAPTURED_VALUE = System.currentTimeMillis();
+        class LocalClassWithCapture {
+            private final String value;
+            private final long capturedValue;
+
+            LocalClassWithCapture(@AnnotationA String p1) {
+                this.value = p1;
+                this.capturedValue = CAPTURED_VALUE;
+            }
+        }
+        return LocalClassWithCapture.class;
+    }
+
+    public static void main(String[] args) throws Throwable {
+        // A local class declared in a static context (0 implicit parameters).
+        class LocalClassStaticContext {
+            private final int value;
+
+            LocalClassStaticContext(@AnnotationA int p0) {
+                this.value = p0;
+            }
+        }
+
+        final long CAPTURED_VALUE = System.currentTimeMillis();
+        // A local class declared in a static context with a capture (1 implicit parameters).
+        class LocalClassStaticContextWithCapture {
+            private final long capturedValue;
+            private final String argumentValue;
+
+            LocalClassStaticContextWithCapture(@AnnotationA String p1) {
+                this.capturedValue = CAPTURED_VALUE;
+                this.argumentValue = p1;
+            }
+        }
+
+        // Another local class declared in a static context with a capture (1 implicit parameters).
+        class LocalClassStaticContextWithCaptureAlternateOrdering {
+            private final String argumentValue;
+            private final long capturedValue;
+
+            LocalClassStaticContextWithCaptureAlternateOrdering(@AnnotationA String p1) {
+                this.argumentValue = p1;
+                this.capturedValue = CAPTURED_VALUE;
+            }
+        }
+
+        DumpConstructorParameterAnnotations(Main.class);
+        DumpConstructorParameterAnnotations(LocalClassStaticContext.class);
+        DumpConstructorParameterAnnotations(LocalClassStaticContextWithCapture.class);
+        DumpConstructorParameterAnnotations(LocalClassStaticContextWithCaptureAlternateOrdering.class);
+        Main m = new Main();
+        DumpConstructorParameterAnnotations(m.getLocalClassWithEnclosingInstanceCapture());
+        DumpConstructorParameterAnnotations(m.getLocalClassWithEnclosingInstanceAndLocalCapture());
+        DumpConstructorParameterAnnotations(Inner.class);
+        DumpConstructorParameterAnnotations(StaticInner.class);
+        DumpConstructorParameterAnnotations(ImportantNumber.class);
+        DumpConstructorParameterAnnotations(BinaryNumber.class);
+        DumpConstructorParameterAnnotations(new AnonymousBase("") {}.getClass());
+    }
+}
diff --git a/test/etc/default-build b/test/etc/default-build
index 4ed2af6..3e6577c 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -137,12 +137,14 @@
 JAVAC_EXPERIMENTAL_ARGS["default-methods"]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS["lambdas"]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS["method-handles"]="-source 1.8 -target 1.8"
+JAVAC_EXPERIMENTAL_ARGS["parameter-annotations"]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS["var-handles"]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS[${DEFAULT_EXPERIMENT}]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS["agents"]="-source 1.8 -target 1.8"
 
 declare -A DX_EXPERIMENTAL_ARGS
 DX_EXPERIMENTAL_ARGS["method-handles"]="--min-sdk-version=26"
+DX_EXPERIMENTAL_ARGS["parameter-annotations"]="--min-sdk-version=25"
 DX_EXPERIMENTAL_ARGS["var-handles"]="--min-sdk-version=26"
 
 while true; do