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."]