1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package art; 18 19 import java.lang.reflect.Field; 20 import java.util.*; 21 import java.util.concurrent.CountDownLatch; 22 public class Test2005 { 23 private static final int NUM_THREADS = 20; 24 private static final String DEFAULT_VAL = "DEFAULT_VALUE"; 25 26 public static final class Transform { 27 public String greetingEnglish; Transform()28 public Transform() { 29 this.greetingEnglish = "Hello"; 30 } sayHi()31 public String sayHi() { 32 return greetingEnglish + " from " + Thread.currentThread().getName(); 33 } 34 } 35 36 /** 37 * base64 encoded class/dex file for 38 * public static final class Transform { 39 * public String greetingEnglish; 40 * public String greetingFrench; 41 * public String greetingDanish; 42 * public String greetingJapanese; 43 * 44 * public Transform() { 45 * this.greetingEnglish = "Hello World"; 46 * this.greetingFrench = "Bonjour le Monde"; 47 * this.greetingDanish = "Hej Verden"; 48 * this.greetingJapanese = "こんにちは世界"; 49 * } 50 * public String sayHi() { 51 * return sayHiEnglish() + ", " + sayHiFrench() + ", " + sayHiDanish() + ", " + 52 * sayHiJapanese() + " from " + Thread.currentThread().getName(); 53 * } 54 * public String sayHiEnglish() { 55 * return greetingEnglish; 56 * } 57 * public String sayHiDanish() { 58 * return greetingDanish; 59 * } 60 * public String sayHiJapanese() { 61 * return greetingJapanese; 62 * } 63 * public String sayHiFrench() { 64 * return greetingFrench; 65 * } 66 * } 67 */ 68 private static final byte[] DEX_BYTES = Base64.getDecoder().decode( 69 "ZGV4CjAzNQAgJ1QXHJ8PAODMKTV14wyH4oKGOMK1yyL4BgAAcAAAAHhWNBIAAAAAAAAAADQGAAAl" 70 + "AAAAcAAAAAkAAAAEAQAABAAAACgBAAAEAAAAWAEAAAwAAAB4AQAAAQAAANgBAAAABQAA+AEAAEoD" 71 + "AABSAwAAVgMAAF4DAABwAwAAfAMAAIkDAACMAwAAkAMAAKoDAAC6AwAA3gMAAP4DAAASBAAAJgQA" 72 + "AEEEAABVBAAAZAQAAG8EAAByBAAAfwQAAIcEAACWBAAAnwQAAK8EAADABAAA0AQAAOIEAADoBAAA" 73 + "7wQAAPwEAAAKBQAAFwUAACYFAAAwBQAANwUAAMUFAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAO" 74 + "AAAADwAAABIAAAAGAAAABQAAAAAAAAAHAAAABgAAAEQDAAAGAAAABwAAAAAAAAASAAAACAAAAAAA" 75 + "AAAAAAUAFwAAAAAABQAYAAAAAAAFABkAAAAAAAUAGgAAAAAAAwACAAAAAAAAABwAAAAAAAAAHQAA" 76 + "AAAAAAAeAAAAAAAAAB8AAAAAAAAAIAAAAAQAAwACAAAABgADAAIAAAAGAAEAFAAAAAYAAAAhAAAA" 77 + "BwACABUAAAAHAAAAFgAAAAAAAAARAAAABAAAAAAAAAAQAAAAJAYAAOsFAAAAAAAABwABAAIAAAAt" 78 + "AwAAQQAAAG4QAwAGAAwAbhAEAAYADAFuEAIABgAMAm4QBQAGAAwDcQAKAAAADARuEAsABAAMBCIF" 79 + "BgBwEAcABQBuIAgABQAaAAEAbiAIAAUAbiAIABUAbiAIAAUAbiAIACUAbiAIAAUAbiAIADUAGgAA" 80 + "AG4gCAAFAG4gCABFAG4QCQAFAAwAEQAAAAIAAQAAAAAAMQMAAAMAAABUEAAAEQAAAAIAAQAAAAAA" 81 + "NQMAAAMAAABUEAEAEQAAAAIAAQAAAAAAOQMAAAMAAABUEAIAEQAAAAIAAQAAAAAAPQMAAAMAAABU" 82 + "EAMAEQAAAAIAAQABAAAAJAMAABQAAABwEAYAAQAaAAUAWxABABoAAwBbEAIAGgAEAFsQAAAaACQA" 83 + "WxADAA4ACQAOPEtLS0sAEAAOABYADgATAA4AHAAOABkADgAAAAABAAAABQAGIGZyb20gAAIsIAAG" 84 + "PGluaXQ+ABBCb25qb3VyIGxlIE1vbmRlAApIZWogVmVyZGVuAAtIZWxsbyBXb3JsZAABTAACTEwA" 85 + "GExhcnQvVGVzdDIwMDUkVHJhbnNmb3JtOwAOTGFydC9UZXN0MjAwNTsAIkxkYWx2aWsvYW5ub3Rh" 86 + "dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph" 87 + "dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp" 88 + "bGRlcjsAEkxqYXZhL2xhbmcvVGhyZWFkOwANVGVzdDIwMDUuamF2YQAJVHJhbnNmb3JtAAFWAAth" 89 + "Y2Nlc3NGbGFncwAGYXBwZW5kAA1jdXJyZW50VGhyZWFkAAdnZXROYW1lAA5ncmVldGluZ0Rhbmlz" 90 + "aAAPZ3JlZXRpbmdFbmdsaXNoAA5ncmVldGluZ0ZyZW5jaAAQZ3JlZXRpbmdKYXBhbmVzZQAEbmFt" 91 + "ZQAFc2F5SGkAC3NheUhpRGFuaXNoAAxzYXlIaUVuZ2xpc2gAC3NheUhpRnJlbmNoAA1zYXlIaUph" 92 + "cGFuZXNlAAh0b1N0cmluZwAFdmFsdWUAiwF+fkQ4eyJjb21waWxhdGlvbi1tb2RlIjoiZGVidWci" 93 + "LCJoYXMtY2hlY2tzdW1zIjpmYWxzZSwibWluLWFwaSI6MSwic2hhLTEiOiI5N2RmNmVkNzlhNzQw" 94 + "ZWVhMzM4MmNiNWRhOTIyYmI1YmJjMDg2NDMzIiwidmVyc2lvbiI6IjIuMC45LWRldiJ9AAfjgZPj" 95 + "gpPjgavjgaHjga/kuJbnlYwAAgIBIhgBAgMCEwQZGxcRAAQBBQABAQEBAQEBAIGABOwFAQH4AwEB" 96 + "jAUBAaQFAQG8BQEB1AUAAAAAAAAAAgAAANwFAADiBQAAGAYAAAAAAAAAAAAAAAAAABAAAAAAAAAA" 97 + "AQAAAAAAAAABAAAAJQAAAHAAAAACAAAACQAAAAQBAAADAAAABAAAACgBAAAEAAAABAAAAFgBAAAF" 98 + "AAAADAAAAHgBAAAGAAAAAQAAANgBAAABIAAABgAAAPgBAAADIAAABgAAACQDAAABEAAAAQAAAEQD" 99 + "AAACIAAAJQAAAEoDAAAEIAAAAgAAANwFAAAAIAAAAQAAAOsFAAADEAAAAgAAABQGAAAGIAAAAQAA" 100 + "ACQGAAAAEAAAAQAAADQGAAA="); 101 run()102 public static void run() throws Exception { 103 Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); 104 doTest(); 105 } 106 107 public static final class MyThread extends Thread { MyThread(CountDownLatch delay, int id)108 public MyThread(CountDownLatch delay, int id) { 109 super("Thread: " + id); 110 this.thr_id = id; 111 this.results = new HashSet<>(); 112 this.finish = false; 113 this.delay = delay; 114 } 115 run()116 public void run() { 117 delay.countDown(); 118 while (!finish) { 119 Transform t = new Transform(); 120 results.add(t.sayHi()); 121 } 122 } 123 finish()124 public void finish() throws Exception { 125 finish = true; 126 this.join(); 127 } 128 Check()129 public void Check() throws Exception { 130 for (String s : results) { 131 if (!s.equals("Hello from " + getName()) 132 && !s.equals("Hello, " + DEFAULT_VAL + ", " + DEFAULT_VAL + ", " + DEFAULT_VAL 133 + " from " + getName()) 134 && !s.equals( 135 "Hello World, Bonjour le Monde, Hej Verden, こんにちは世界 from " + getName())) { 136 System.out.println("FAIL " + thr_id + ": Unexpected result: " + s); 137 } 138 } 139 } 140 141 public HashSet<String> results; 142 public volatile boolean finish; 143 public int thr_id; 144 public CountDownLatch delay; 145 } 146 startThreads(int num_threads)147 public static MyThread[] startThreads(int num_threads) throws Exception { 148 CountDownLatch cdl = new CountDownLatch(num_threads); 149 MyThread[] res = new MyThread[num_threads]; 150 for (int i = 0; i < num_threads; i++) { 151 res[i] = new MyThread(cdl, i); 152 res[i].start(); 153 } 154 cdl.await(); 155 return res; 156 } finishThreads(MyThread[] thrs)157 public static void finishThreads(MyThread[] thrs) throws Exception { 158 for (MyThread t : thrs) { 159 t.finish(); 160 } 161 for (MyThread t : thrs) { 162 t.Check(); 163 } 164 } 165 doRedefinition()166 public static void doRedefinition() throws Exception { 167 // Get the current set of fields. 168 Field[] fields = Transform.class.getDeclaredFields(); 169 // Get all the threads in the 'main' thread group 170 ThreadGroup mytg = Thread.currentThread().getThreadGroup(); 171 Thread[] all_threads = new Thread[mytg.activeCount()]; 172 mytg.enumerate(all_threads); 173 Set<Thread> thread_set = new HashSet<>(Arrays.asList(all_threads)); 174 // We don't want to suspend ourself, that would cause a deadlock. 175 thread_set.remove(Thread.currentThread()); 176 // If some of the other threads finished between calling mytg.activeCount and enumerate we will 177 // have nulls. These nulls are interpreted as currentThread by SuspendThreadList so we want to 178 // get rid of them. 179 thread_set.remove(null); 180 // Suspend them. 181 Suspension.suspendList(thread_set.toArray(new Thread[0])); 182 // Actual redefine. 183 Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES); 184 // Get the new fields. 185 Field[] new_fields = Transform.class.getDeclaredFields(); 186 Set<Field> field_set = new HashSet(Arrays.asList(new_fields)); 187 field_set.removeAll(Arrays.asList(fields)); 188 // Initialize the new fields on the old objects and resume. 189 UpdateFieldValuesAndResumeThreads(thread_set.toArray(new Thread[0]), 190 Transform.class, 191 field_set.toArray(new Field[0]), 192 DEFAULT_VAL); 193 } 194 doTest()195 public static void doTest() throws Exception { 196 // Force the Transform class to be initialized. We are suspending the remote 197 // threads so if one of them is in the class initialization (and therefore 198 // has a monitor lock on the class object) the redefinition will deadlock 199 // waiting for the clinit to finish and the monitor to be released. 200 if (null == Class.forName("art.Test2005$Transform")) { 201 throw new Error("No class!"); 202 } 203 MyThread[] threads = startThreads(NUM_THREADS); 204 205 doRedefinition(); 206 finishThreads(threads); 207 } UpdateFieldValuesAndResumeThreads( Thread[] t, Class<?> redefined_class, Field[] new_fields, String default_val)208 public static native void UpdateFieldValuesAndResumeThreads( 209 Thread[] t, Class<?> redefined_class, Field[] new_fields, String default_val); 210 } 211