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