Ensure structural redefinition can shadow functions/fields

Previously when we structurally redefined a class and a new field or
function shadowed a super type field or function the shadowed version
might still get called. This was due to the old version remaining in
the dex-caches of other classes. To fix this we just remove from all
dex-caches any entries that might be (invisibly) shadowed by a new
function/field. This is any non-virtual methods or non-private field
of a supertype of the redefined class with the same name & signature
as a method/field in the redefined class.

Test: ./test.py --host
Bug: 134162467

Change-Id: Iae8e7a3e7f8ecedbd2f314816b140156fce3e5fd
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 46b7d11..50dc09c 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -112,6 +112,7 @@
 #include "non_debuggable_classes.h"
 #include "obj_ptr.h"
 #include "object_lock.h"
+#include "reflective_value_visitor.h"
 #include "runtime.h"
 #include "runtime_globals.h"
 #include "stack.h"
@@ -1640,6 +1641,11 @@
     }
 
     cur_data->SetNewClassObject(nc.Get());
+    // We really want to be able to resolve to the new class-object using this dex-cache for
+    // verification work. Since we haven't put it in the class-table yet we wll just manually add it
+    // to the dex-cache.
+    // TODO: We should maybe do this in a better spot.
+    cur_data->GetNewDexCache()->SetResolvedType(nc->GetDexTypeIndex(), nc.Get());
   }
   return true;
 }
@@ -2087,27 +2093,84 @@
   replacement->SetLockWord(orig->GetLockWord(false), false);
   orig->SetLockWord(art::LockWord::Default(), false);
   // Update live pointers in ART code.
+  auto could_change_resolution_of = [&](auto* field_or_method,
+                                        const auto& info) REQUIRES(art::Locks::mutator_lock_) {
+    constexpr bool is_method = std::is_same_v<art::ArtMethod*, decltype(field_or_method)>;
+    static_assert(is_method || std::is_same_v<art::ArtField*, decltype(field_or_method)>,
+                  "Input is not field or method!");
+    // Only dex-cache is used for resolution
+    if (LIKELY(info.GetType() != art::ReflectionSourceType::kSourceDexCacheResolvedField &&
+               info.GetType() != art::ReflectionSourceType::kSourceDexCacheResolvedMethod)) {
+      return false;
+    }
+    if constexpr (is_method) {
+      // Only direct methods are used without further indirection through a vtable/IFTable.
+      // Constructors cannot be shadowed.
+      if (LIKELY(!field_or_method->IsDirect() || field_or_method->IsConstructor())) {
+        return false;
+      }
+    } else {
+      // Only non-private fields can be shadowed in a manner that's visible.
+      if (LIKELY(field_or_method->IsPrivate())) {
+        return false;
+      }
+    }
+    // We can only shadow things from our superclasses
+    if (LIKELY(!field_or_method->GetDeclaringClass()->IsAssignableFrom(orig))) {
+      return false;
+    }
+    if constexpr (is_method) {
+      auto direct_methods = replacement->GetDirectMethods(art::kRuntimePointerSize);
+      return std::find_if(direct_methods.begin(),
+                          direct_methods.end(),
+                          [&](art::ArtMethod& m) REQUIRES(art::Locks::mutator_lock_) {
+                            return UNLIKELY(m.HasSameNameAndSignature(field_or_method));
+                          }) != direct_methods.end();
+    } else {
+      auto pred = [&](art::ArtField& f) REQUIRES(art::Locks::mutator_lock_) {
+        return std::string_view(f.GetName()) == std::string_view(field_or_method->GetName()) &&
+               std::string_view(f.GetTypeDescriptor()) ==
+                   std::string_view(field_or_method->GetTypeDescriptor());
+      };
+      if (field_or_method->IsStatic()) {
+        auto sfields = replacement->GetSFields();
+        return std::find_if(sfields.begin(), sfields.end(), pred) != sfields.end();
+      } else {
+        auto ifields = replacement->GetIFields();
+        return std::find_if(ifields.begin(), ifields.end(), pred) != ifields.end();
+      }
+    }
+  };
   // TODO Performing 2 stack-walks back to back isn't the greatest. We might want to try to combine
   // it with the one ReplaceReferences does. Doing so would be rather complicated though.
   driver_->runtime_->VisitReflectiveTargets(
       [&](art::ArtField* f, const auto& info) REQUIRES(art::Locks::mutator_lock_) {
         DCHECK(f != nullptr) << info;
         auto it = field_map.find(f);
-        if (it == field_map.end()) {
-          return f;
+        if (it != field_map.end()) {
+          VLOG(plugin) << "Updating " << info << " object for (field) "
+                       << it->second->PrettyField();
+          return it->second;
+        } else if (UNLIKELY(could_change_resolution_of(f, info))) {
+          // Resolution might change. Just clear the resolved value.
+          VLOG(plugin) << "Clearing resolution " << info << " for (field) " << f->PrettyField();
+          return static_cast<art::ArtField*>(nullptr);
         }
-        VLOG(plugin) << "Updating " << info << " object for (field) " << it->second->PrettyField();
-        return it->second;
+        return f;
       },
       [&](art::ArtMethod* m, const auto& info) REQUIRES(art::Locks::mutator_lock_) {
         DCHECK(m != nullptr) << info;
         auto it = method_map.find(m);
-        if (it == method_map.end()) {
-          return m;
+        if (it != method_map.end()) {
+          VLOG(plugin) << "Updating " << info << " object for (method) "
+                      << it->second->PrettyMethod();
+          return it->second;
+        } else if (UNLIKELY(could_change_resolution_of(m, info))) {
+          // Resolution might change. Just clear the resolved value.
+          VLOG(plugin) << "Clearing resolution " << info << " for (method) " << m->PrettyMethod();
+          return static_cast<art::ArtMethod*>(nullptr);
         }
-        VLOG(plugin) << "Updating " << info << " object for (method) "
-                     << it->second->PrettyMethod();
-        return it->second;
+        return m;
       });
 
   // Force every frame of every thread to deoptimize (any frame might have eg offsets compiled in).
diff --git a/runtime/art_field.h b/runtime/art_field.h
index e44517e..bc2c399 100644
--- a/runtime/art_field.h
+++ b/runtime/art_field.h
@@ -75,6 +75,10 @@
     return (GetAccessFlags() & kAccFinal) != 0;
   }
 
+  bool IsPrivate() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return (GetAccessFlags() & kAccPrivate) != 0;
+  }
+
   uint32_t GetDexFieldIndex() {
     return field_dex_idx_;
   }
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index b503f0a..96fc403 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -182,7 +182,11 @@
     ArtField* new_val = visitor->VisitField(
         pair.object, DexCacheSourceInfo(kSourceDexCacheResolvedField, pair.index, this));
     if (UNLIKELY(new_val != pair.object)) {
-      pair.object = new_val;
+      if (new_val == nullptr) {
+        pair = FieldDexCachePair(nullptr, FieldDexCachePair::InvalidIndexForSlot(i));
+      } else {
+        pair.object = new_val;
+      }
       SetNativePairPtrSize(GetResolvedFields(), i, pair, kRuntimePointerSize);
     }
   }
@@ -194,7 +198,11 @@
     ArtMethod* new_val = visitor->VisitMethod(
         pair.object, DexCacheSourceInfo(kSourceDexCacheResolvedMethod, pair.index, this));
     if (UNLIKELY(new_val != pair.object)) {
-      pair.object = new_val;
+      if (new_val == nullptr) {
+        pair = MethodDexCachePair(nullptr, MethodDexCachePair::InvalidIndexForSlot(i));
+      } else {
+        pair.object = new_val;
+      }
       SetNativePairPtrSize(GetResolvedMethods(), i, pair, kRuntimePointerSize);
     }
   }
diff --git a/runtime/reflective_value_visitor.h b/runtime/reflective_value_visitor.h
index 0b09a0b..3a72760 100644
--- a/runtime/reflective_value_visitor.h
+++ b/runtime/reflective_value_visitor.h
@@ -109,6 +109,10 @@
     os << "Type=" << type_;
   }
 
+  ReflectionSourceType GetType() const {
+    return type_;
+  }
+
  private:
   const ReflectionSourceType type_;
 
diff --git a/test/1997-structural-shadow-method/expected.txt b/test/1997-structural-shadow-method/expected.txt
new file mode 100644
index 0000000..3a8b8de
--- /dev/null
+++ b/test/1997-structural-shadow-method/expected.txt
@@ -0,0 +1,6 @@
+Hello!
+Hello!
+Hello!
+Hello World!
+Hello World!
+Hello World!
diff --git a/test/1997-structural-shadow-method/info.txt b/test/1997-structural-shadow-method/info.txt
new file mode 100644
index 0000000..71e3bfc
--- /dev/null
+++ b/test/1997-structural-shadow-method/info.txt
@@ -0,0 +1 @@
+Test structural redefinition when the method being added was resolvable previously.
diff --git a/test/1997-structural-shadow-method/run b/test/1997-structural-shadow-method/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1997-structural-shadow-method/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1997-structural-shadow-method/src/Main.java b/test/1997-structural-shadow-method/src/Main.java
new file mode 100644
index 0000000..3c9bc85
--- /dev/null
+++ b/test/1997-structural-shadow-method/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1997.run();
+  }
+}
diff --git a/test/1997-structural-shadow-method/src/art/Redefinition.java b/test/1997-structural-shadow-method/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1997-structural-shadow-method/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1997-structural-shadow-method/src/art/Test1997.java b/test/1997-structural-shadow-method/src/art/Test1997.java
new file mode 100644
index 0000000..7309a31
--- /dev/null
+++ b/test/1997-structural-shadow-method/src/art/Test1997.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package art;
+
+import java.util.Base64;
+
+public class Test1997 {
+
+  public static class SuperTransform {
+    // We will be shadowing this function.
+    public static void sayHi() {
+      System.out.println("Hello!");
+    }
+  }
+
+  // The class we will be transforming.
+  public static class Transform extends SuperTransform {
+    public static void sayHiTwice() {
+      Transform.sayHi();
+      Transform.sayHi();
+    }
+  }
+
+  // public static class Transform extends SuperTransform {
+  //   public static void sayHiTwice() {
+  //     Transform.sayHi();
+  //     Transform.sayHi();
+  //   }
+  //   public static void sayHi() {
+  //     System.out.println("Hello World!");
+  //   }
+  // }
+  private static final byte[] DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+              "ZGV4CjAzNQA9wdy7Lgbrv+sD+wixborREr0maZCK5yqABAAAcAAAAHhWNBIAAAAAAAAAALwDAAAW"
+                  + "AAAAcAAAAAkAAADIAAAAAgAAAOwAAAABAAAABAEAAAUAAAAMAQAAAQAAADQBAAAsAwAAVAEAAMIB"
+                  + "AADKAQAA2AEAAPcBAAARAgAAIQIAAEUCAABlAgAAfAIAAJACAACkAgAAswIAAL4CAADBAgAAxQIA"
+                  + "ANICAADYAgAA3QIAAOYCAADtAgAA+QIAAAADAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAA"
+                  + "CQAAAAwAAAAMAAAACAAAAAAAAAANAAAACAAAALwBAAAHAAUAEAAAAAAAAAAAAAAAAQAAAAAAAAAB"
+                  + "AAAAEgAAAAEAAAATAAAABQABABEAAAABAAAAAQAAAAAAAAAAAAAACgAAAKwDAACHAwAAAAAAAAEA"
+                  + "AQABAAAAqgEAAAQAAABwEAAAAAAOAAIAAAACAAAArgEAAAgAAABiAAAAGgEBAG4gBAAQAA4AAAAA"
+                  + "AAAAAACzAQAABwAAAHEAAgAAAHEAAgAAAA4ADwAOABUADngAEQAOPDwAAAAAAQAAAAYABjxpbml0"
+                  + "PgAMSGVsbG8gV29ybGQhAB1MYXJ0L1Rlc3QxOTk3JFN1cGVyVHJhbnNmb3JtOwAYTGFydC9UZXN0"
+                  + "MTk5NyRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTk3OwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv"
+                  + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABVMamF2YS9pby9Qcmlu"
+                  + "dFN0cmVhbTsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AA1UZXN0MTk5"
+                  + "Ny5qYXZhAAlUcmFuc2Zvcm0AAVYAAlZMAAthY2Nlc3NGbGFncwAEbmFtZQADb3V0AAdwcmludGxu"
+                  + "AAVzYXlIaQAKc2F5SGlUd2ljZQAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1"
+                  + "ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2"
+                  + "ZDg4YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AAgMBFBgCAgQCDgQJDxcLAAADAAGBgATU"
+                  + "AgEJ7AIBCYwDAAAAAAAAAAIAAAB4AwAAfgMAAKADAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAEAAAAA"
+                  + "AAAAAQAAABYAAABwAAAAAgAAAAkAAADIAAAAAwAAAAIAAADsAAAABAAAAAEAAAAEAQAABQAAAAUA"
+                  + "AAAMAQAABgAAAAEAAAA0AQAAASAAAAMAAABUAQAAAyAAAAMAAACqAQAAARAAAAEAAAC8AQAAAiAA"
+                  + "ABYAAADCAQAABCAAAAIAAAB4AwAAACAAAAEAAACHAwAAAxAAAAIAAACcAwAABiAAAAEAAACsAwAA"
+                  + "ABAAAAEAAAC8AwAA");
+
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  public static void doTest() throws Exception {
+    Transform.sayHiTwice();
+    Transform.sayHi();
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+    Transform.sayHiTwice();
+    Transform.sayHi();
+  }
+}
diff --git a/test/1998-structural-shadow-field/expected.txt b/test/1998-structural-shadow-field/expected.txt
new file mode 100644
index 0000000..9ae530e
--- /dev/null
+++ b/test/1998-structural-shadow-field/expected.txt
@@ -0,0 +1,4 @@
+Hello
+Hello
+null
+Hello
diff --git a/test/1998-structural-shadow-field/info.txt b/test/1998-structural-shadow-field/info.txt
new file mode 100644
index 0000000..71e3bfc
--- /dev/null
+++ b/test/1998-structural-shadow-field/info.txt
@@ -0,0 +1 @@
+Test structural redefinition when the method being added was resolvable previously.
diff --git a/test/1998-structural-shadow-field/run b/test/1998-structural-shadow-field/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1998-structural-shadow-field/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1998-structural-shadow-field/src/Main.java b/test/1998-structural-shadow-field/src/Main.java
new file mode 100644
index 0000000..f6aeca5
--- /dev/null
+++ b/test/1998-structural-shadow-field/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1998.run();
+  }
+}
diff --git a/test/1998-structural-shadow-field/src/art/Redefinition.java b/test/1998-structural-shadow-field/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1998-structural-shadow-field/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1998-structural-shadow-field/src/art/Test1998.java b/test/1998-structural-shadow-field/src/art/Test1998.java
new file mode 100644
index 0000000..3fda936
--- /dev/null
+++ b/test/1998-structural-shadow-field/src/art/Test1998.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package art;
+
+import java.util.Base64;
+
+public class Test1998 {
+
+  public static class SuperTransform {
+    public static String greeting = "Hello";
+  }
+
+  // The class we will be transforming.
+  public static class Transform extends SuperTransform { }
+
+  // public static class Transform extends SuperTransform {
+  //   public static String greeting;
+  // }
+  private static final byte[] DEX_BYTES =
+      Base64.getDecoder()
+          .decode(
+"ZGV4CjAzNQCYmnoWz4BqygrZQM4zf/mJ/25+dM86MHKAAwAAcAAAAHhWNBIAAAAAAAAAAMgCAAAP" +
+"AAAAcAAAAAcAAACsAAAAAQAAAMgAAAABAAAA1AAAAAIAAADcAAAAAQAAAOwAAAB0AgAADAEAACgB" +
+"AAAwAQAATwEAAGkBAAB5AQAAnQEAAL0BAADRAQAA4AEAAOsBAADuAQAA+wEAAAUCAAALAgAAEgIA" +
+"AAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAkAAAAJAAAABgAAAAAAAAABAAUACwAAAAAAAAAAAAAA" +
+"AQAAAAAAAAABAAAAAQAAAAAAAAAAAAAABwAAALgCAACZAgAAAAAAAAEAAQABAAAAJAEAAAQAAABw" +
+"EAAAAAAOAAUADgAGPGluaXQ+AB1MYXJ0L1Rlc3QxOTk4JFN1cGVyVHJhbnNmb3JtOwAYTGFydC9U" +
+"ZXN0MTk5OCRUcmFuc2Zvcm07AA5MYXJ0L1Rlc3QxOTk4OwAiTGRhbHZpay9hbm5vdGF0aW9uL0Vu" +
+"Y2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5n" +
+"L1N0cmluZzsADVRlc3QxOTk4LmphdmEACVRyYW5zZm9ybQABVgALYWNjZXNzRmxhZ3MACGdyZWV0" +
+"aW5nAARuYW1lAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFw" +
+"aSI6MSwic2hhLTEiOiI2MGRhNGQ2N2IzODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwi" +
+"dmVyc2lvbiI6IjEuNy4xMi1kZXYifQACAwENGAICBAIKBAkMFwgBAAEAAAkBgYAEjAIAAAAAAAAA" +
+"AgAAAIoCAACQAgAArAIAAAAAAAAAAAAAAAAAAA8AAAAAAAAAAQAAAAAAAAABAAAADwAAAHAAAAAC" +
+"AAAABwAAAKwAAAADAAAAAQAAAMgAAAAEAAAAAQAAANQAAAAFAAAAAgAAANwAAAAGAAAAAQAAAOwA" +
+"AAABIAAAAQAAAAwBAAADIAAAAQAAACQBAAACIAAADwAAACgBAAAEIAAAAgAAAIoCAAAAIAAAAQAA" +
+"AJkCAAADEAAAAgAAAKgCAAAGIAAAAQAAALgCAAAAEAAAAQAAAMgCAAA=");
+
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  public static void doTest() throws Exception {
+    System.out.println(Transform.greeting);
+    System.out.println(SuperTransform.greeting);
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+    System.out.println(Transform.greeting);
+    System.out.println(SuperTransform.greeting);
+  }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 0134a54..cd66472 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1145,7 +1145,9 @@
                   "1990-structural-bad-verify",
                   "1991-hello-structural-retransform",
                   "1992-retransform-no-such-field",
-                  "1993-fallback-non-structural"
+                  "1993-fallback-non-structural",
+                  "1997-structural-shadow-method",
+                  "1998-structural-shadow-field"
                 ],
         "variant": "jvm",
         "description": ["Doesn't run on RI."]