• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 
18 package com.android.tools.layoutlib.create;
19 
20 
21 import org.junit.After;
22 import org.junit.Before;
23 import org.junit.Test;
24 import org.objectweb.asm.ClassReader;
25 import org.objectweb.asm.ClassVisitor;
26 import org.objectweb.asm.FieldVisitor;
27 import org.objectweb.asm.MethodVisitor;
28 import org.objectweb.asm.Type;
29 
30 import java.io.ByteArrayOutputStream;
31 import java.io.File;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.lang.reflect.InvocationTargetException;
35 import java.lang.reflect.Method;
36 import java.net.URL;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.Enumeration;
40 import java.util.HashSet;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.TreeMap;
44 import java.util.zip.ZipEntry;
45 import java.util.zip.ZipFile;
46 
47 import static org.junit.Assert.assertArrayEquals;
48 import static org.junit.Assert.assertEquals;
49 import static org.junit.Assert.assertFalse;
50 import static org.junit.Assert.assertNotNull;
51 import static org.junit.Assert.assertTrue;
52 
53 /**
54  * Unit tests for some methods of {@link AsmGenerator}.
55  */
56 public class AsmGeneratorTest {
57 
58     private static final String[] EMPTY_STRING_ARRAY = new String[0];
59     private MockLog mLog;
60     private ArrayList<String> mOsJarPath;
61     private String mOsDestJar;
62     private File mTempFile;
63 
64     // ASM internal name for the the class in java package that should be refactored.
65     private static final String JAVA_CLASS_NAME = "java/lang/JavaClass";
66 
67     @Before
setUp()68     public void setUp() throws Exception {
69         mLog = new MockLog();
70         URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
71 
72         mOsJarPath = new ArrayList<>();
73         //noinspection ConstantConditions
74         mOsJarPath.add(url.getFile());
75 
76         mTempFile = File.createTempFile("mock", ".jar");
77         mOsDestJar = mTempFile.getAbsolutePath();
78         mTempFile.deleteOnExit();
79     }
80 
81     @After
tearDown()82     public void tearDown() throws Exception {
83         if (mTempFile != null) {
84             mTempFile.delete();
85             mTempFile = null;
86         }
87     }
88 
89     @Test
testClassRenaming()90     public void testClassRenaming() throws IOException, LogAbortException {
91 
92         ICreateInfo ci = new ICreateInfo() {
93             @Override
94             public Class<?>[] getInjectedClasses() {
95                 // classes to inject in the final JAR
96                 return new Class<?>[0];
97             }
98 
99             @Override
100             public String[] getDelegateMethods() {
101                 return EMPTY_STRING_ARRAY;
102             }
103 
104             @Override
105             public String[] getDelegateClassNatives() {
106                 return EMPTY_STRING_ARRAY;
107             }
108 
109             @Override
110             public String[] getOverriddenMethods() {
111                 // methods to force override
112                 return EMPTY_STRING_ARRAY;
113             }
114 
115             @Override
116             public String[] getRenamedClasses() {
117                 // classes to rename (so that we can replace them)
118                 return new String[] {
119                         "mock_android.view.View", "mock_android.view._Original_View",
120                         "not.an.actual.ClassName", "anoter.fake.NewClassName",
121                 };
122             }
123 
124             @Override
125             public String[] getJavaPkgClasses() {
126               return EMPTY_STRING_ARRAY;
127             }
128 
129             @Override
130             public Set<String> getExcludedClasses() {
131                 return null;
132             }
133 
134             @Override
135             public String[] getDeleteReturns() {
136                  // methods deleted from their return type.
137                 return EMPTY_STRING_ARRAY;
138             }
139 
140             @Override
141             public String[] getPromotedFields() {
142                 return EMPTY_STRING_ARRAY;
143             }
144 
145             @Override
146             public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
147                 return Collections.emptyMap();
148             }
149         };
150 
151         AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
152 
153         AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
154                 null,                 // derived from
155                 new String[] {        // include classes
156                     "**"
157                 },
158                 Collections.<String>emptySet() /* excluded classes */,
159                 new String[]{} /* include files */);
160         aa.analyze();
161         agen.generate();
162 
163         Set<String> notRenamed = agen.getClassesNotRenamed();
164         assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
165 
166     }
167 
168     @Test
testClassRefactoring()169     public void testClassRefactoring() throws IOException, LogAbortException {
170         ICreateInfo ci = new ICreateInfo() {
171             @Override
172             public Class<?>[] getInjectedClasses() {
173                 // classes to inject in the final JAR
174                 return new Class<?>[] {
175                         com.android.tools.layoutlib.create.dataclass.JavaClass.class
176                 };
177             }
178 
179             @Override
180             public String[] getDelegateMethods() {
181                 return EMPTY_STRING_ARRAY;
182             }
183 
184             @Override
185             public String[] getDelegateClassNatives() {
186                 return EMPTY_STRING_ARRAY;
187             }
188 
189             @Override
190             public String[] getOverriddenMethods() {
191                 // methods to force override
192                 return EMPTY_STRING_ARRAY;
193             }
194 
195             @Override
196             public String[] getRenamedClasses() {
197                 // classes to rename (so that we can replace them)
198                 return EMPTY_STRING_ARRAY;
199             }
200 
201             @Override
202             public String[] getJavaPkgClasses() {
203              // classes to refactor (so that we can replace them)
204                 return new String[] {
205                         "java.lang.JavaClass", "com.android.tools.layoutlib.create.dataclass.JavaClass",
206                 };
207             }
208 
209             @Override
210             public Set<String> getExcludedClasses() {
211                 return Collections.singleton("java.lang.JavaClass");
212             }
213 
214             @Override
215             public String[] getDeleteReturns() {
216                  // methods deleted from their return type.
217                 return EMPTY_STRING_ARRAY;
218             }
219 
220             @Override
221             public String[] getPromotedFields() {
222                 return EMPTY_STRING_ARRAY;
223             }
224 
225             @Override
226             public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
227                 return Collections.emptyMap();
228             }
229         };
230 
231         AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
232 
233         AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
234                 null,                 // derived from
235                 new String[] {        // include classes
236                     "**"
237                 },
238                 Collections.<String>emptySet(),
239                 new String[] {        /* include files */
240                     "mock_android/data/data*"
241                 });
242         aa.analyze();
243         agen.generate();
244         Map<String, ClassReader> output = new TreeMap<>();
245         Map<String, InputStream> filesFound = new TreeMap<>();
246         parseZip(mOsDestJar, output, filesFound);
247         boolean injectedClassFound = false;
248         for (ClassReader cr: output.values()) {
249             TestClassVisitor cv = new TestClassVisitor();
250             cr.accept(cv, 0);
251             injectedClassFound |= cv.mInjectedClassFound;
252         }
253         assertTrue(injectedClassFound);
254         assertArrayEquals(new String[] {"mock_android/data/dataFile"},
255                 filesFound.keySet().toArray());
256     }
257 
258     @Test
testClassExclusion()259     public void testClassExclusion() throws IOException, LogAbortException {
260         ICreateInfo ci = new ICreateInfo() {
261             @Override
262             public Class<?>[] getInjectedClasses() {
263                 return new Class<?>[0];
264             }
265 
266             @Override
267             public String[] getDelegateMethods() {
268                 return EMPTY_STRING_ARRAY;
269             }
270 
271             @Override
272             public String[] getDelegateClassNatives() {
273                 return EMPTY_STRING_ARRAY;
274             }
275 
276             @Override
277             public String[] getOverriddenMethods() {
278                 // methods to force override
279                 return EMPTY_STRING_ARRAY;
280             }
281 
282             @Override
283             public String[] getRenamedClasses() {
284                 // classes to rename (so that we can replace them)
285                 return EMPTY_STRING_ARRAY;
286             }
287 
288             @Override
289             public String[] getJavaPkgClasses() {
290                 // classes to refactor (so that we can replace them)
291                 return EMPTY_STRING_ARRAY;
292             }
293 
294             @Override
295             public Set<String> getExcludedClasses() {
296                 Set<String> set = new HashSet<>(2);
297                 set.add("mock_android.dummy.InnerTest");
298                 set.add("java.lang.JavaClass");
299                 return set;
300             }
301 
302             @Override
303             public String[] getDeleteReturns() {
304                 // methods deleted from their return type.
305                 return EMPTY_STRING_ARRAY;
306             }
307 
308             @Override
309             public String[] getPromotedFields() {
310                 return EMPTY_STRING_ARRAY;
311             }
312 
313             @Override
314             public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
315                 return Collections.emptyMap();
316             }
317         };
318 
319         AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
320         Set<String> excludedClasses = ci.getExcludedClasses();
321         AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
322                 null,                 // derived from
323                 new String[] {        // include classes
324                         "**"
325                 },
326                 excludedClasses,
327                 new String[] {        /* include files */
328                         "mock_android/data/data*"
329                 });
330         aa.analyze();
331         agen.generate();
332         Map<String, ClassReader> output = new TreeMap<>();
333         Map<String, InputStream> filesFound = new TreeMap<>();
334         parseZip(mOsDestJar, output, filesFound);
335         for (String s : output.keySet()) {
336             assertFalse(excludedClasses.contains(s));
337         }
338         assertArrayEquals(new String[] {"mock_android/data/dataFile"},
339                 filesFound.keySet().toArray());
340     }
341 
342     @Test
testMethodInjection()343     public void testMethodInjection() throws IOException, LogAbortException,
344             ClassNotFoundException, IllegalAccessException, InstantiationException,
345             NoSuchMethodException, InvocationTargetException {
346         ICreateInfo ci = new ICreateInfo() {
347             @Override
348             public Class<?>[] getInjectedClasses() {
349                 return new Class<?>[0];
350             }
351 
352             @Override
353             public String[] getDelegateMethods() {
354                 return EMPTY_STRING_ARRAY;
355             }
356 
357             @Override
358             public String[] getDelegateClassNatives() {
359                 return EMPTY_STRING_ARRAY;
360             }
361 
362             @Override
363             public String[] getOverriddenMethods() {
364                 // methods to force override
365                 return EMPTY_STRING_ARRAY;
366             }
367 
368             @Override
369             public String[] getRenamedClasses() {
370                 // classes to rename (so that we can replace them)
371                 return EMPTY_STRING_ARRAY;
372             }
373 
374             @Override
375             public String[] getJavaPkgClasses() {
376                 // classes to refactor (so that we can replace them)
377                 return EMPTY_STRING_ARRAY;
378             }
379 
380             @Override
381             public Set<String> getExcludedClasses() {
382                 return Collections.emptySet();
383             }
384 
385             @Override
386             public String[] getDeleteReturns() {
387                 // methods deleted from their return type.
388                 return EMPTY_STRING_ARRAY;
389             }
390 
391             @Override
392             public String[] getPromotedFields() {
393                 return EMPTY_STRING_ARRAY;
394             }
395 
396             @Override
397             public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
398                 return Collections.singletonMap("mock_android.util.EmptyArray",
399                         InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
400             }
401         };
402 
403         AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
404         AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
405                 null,                 // derived from
406                 new String[] {        // include classes
407                         "**"
408                 },
409                 ci.getExcludedClasses(),
410                 new String[] {        /* include files */
411                         "mock_android/data/data*"
412                 });
413         aa.analyze();
414         agen.generate();
415         Map<String, ClassReader> output = new TreeMap<>();
416         Map<String, InputStream> filesFound = new TreeMap<>();
417         parseZip(mOsDestJar, output, filesFound);
418         final String modifiedClass = "mock_android.util.EmptyArray";
419         final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class");
420         ZipFile zipFile = new ZipFile(mOsDestJar);
421         ZipEntry entry = zipFile.getEntry(modifiedClassPath);
422         assertNotNull(entry);
423         final byte[] bytes;
424         try (InputStream inputStream = zipFile.getInputStream(entry)) {
425             bytes = getByteArray(inputStream);
426         }
427         ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) {
428             @Override
429             protected Class<?> findClass(String name) throws ClassNotFoundException {
430                 if (name.equals(modifiedClass)) {
431                     return defineClass(null, bytes, 0, bytes.length);
432                 }
433                 throw new ClassNotFoundException(name + " not found.");
434             }
435         };
436         Class<?> emptyArrayClass = classLoader.loadClass(modifiedClass);
437         Object emptyArrayInstance = emptyArrayClass.newInstance();
438         Method method = emptyArrayClass.getMethod("getFrameworkClassLoader");
439         Object cl = method.invoke(emptyArrayInstance);
440         assertEquals(classLoader, cl);
441     }
442 
getByteArray(InputStream stream)443     private static byte[] getByteArray(InputStream stream) throws IOException {
444         ByteArrayOutputStream bos = new ByteArrayOutputStream();
445         byte[] buffer = new byte[1024];
446         int read;
447         while ((read = stream.read(buffer, 0, buffer.length)) > -1) {
448             bos.write(buffer, 0, read);
449         }
450         return bos.toByteArray();
451     }
452 
parseZip(String jarPath, Map<String, ClassReader> classes, Map<String, InputStream> filesFound)453     private void parseZip(String jarPath,
454             Map<String, ClassReader> classes,
455             Map<String, InputStream> filesFound) throws IOException {
456 
457             ZipFile zip = new ZipFile(jarPath);
458             Enumeration<? extends ZipEntry> entries = zip.entries();
459             ZipEntry entry;
460             while (entries.hasMoreElements()) {
461                 entry = entries.nextElement();
462                 if (entry.getName().endsWith(".class")) {
463                     ClassReader cr = new ClassReader(zip.getInputStream(entry));
464                     String className = classReaderToClassName(cr);
465                     classes.put(className, cr);
466                 } else {
467                     filesFound.put(entry.getName(), zip.getInputStream(entry));
468                 }
469             }
470 
471     }
472 
classReaderToClassName(ClassReader classReader)473     private String classReaderToClassName(ClassReader classReader) {
474         if (classReader == null) {
475             return null;
476         } else {
477             return classReader.getClassName().replace('/', '.');
478         }
479     }
480 
481     private class TestClassVisitor extends ClassVisitor {
482 
483         boolean mInjectedClassFound = false;
484 
TestClassVisitor()485         TestClassVisitor() {
486             super(Main.ASM_VERSION);
487         }
488 
489         @Override
visit(int version, int access, String name, String signature, String superName, String[] interfaces)490         public void visit(int version, int access, String name, String signature,
491                 String superName, String[] interfaces) {
492             assertTrue(!getBase(name).equals(JAVA_CLASS_NAME));
493             if (name.equals("com/android/tools/layoutlib/create/dataclass/JavaClass")) {
494                 mInjectedClassFound = true;
495             }
496             super.visit(version, access, name, signature, superName, interfaces);
497         }
498 
499         @Override
visitField(int access, String name, String desc, String signature, Object value)500         public FieldVisitor visitField(int access, String name, String desc,
501                 String signature, Object value) {
502             assertTrue(testType(Type.getType(desc)));
503             return super.visitField(access, name, desc, signature, value);
504         }
505 
506         @SuppressWarnings("hiding")
507         @Override
visitMethod(int access, String name, String desc, String signature, String[] exceptions)508         public MethodVisitor visitMethod(int access, String name, String desc,
509                 String signature, String[] exceptions) {
510             MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
511             return new MethodVisitor(Main.ASM_VERSION, mv) {
512 
513                 @Override
514                 public void visitFieldInsn(int opcode, String owner, String name,
515                         String desc) {
516                     assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
517                     assertTrue(testType(Type.getType(desc)));
518                     super.visitFieldInsn(opcode, owner, name, desc);
519                 }
520 
521                 @Override
522                 public void visitLdcInsn(Object cst) {
523                     if (cst instanceof Type) {
524                         assertTrue(testType((Type)cst));
525                     }
526                     super.visitLdcInsn(cst);
527                 }
528 
529                 @Override
530                 public void visitTypeInsn(int opcode, String type) {
531                     assertTrue(!getBase(type).equals(JAVA_CLASS_NAME));
532                     super.visitTypeInsn(opcode, type);
533                 }
534 
535                 @Override
536                 public void visitMethodInsn(int opcode, String owner, String name,
537                         String desc, boolean itf) {
538                     assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
539                     assertTrue(testType(Type.getType(desc)));
540                     super.visitMethodInsn(opcode, owner, name, desc, itf);
541                 }
542 
543             };
544         }
545 
testType(Type type)546         private boolean testType(Type type) {
547             int sort = type.getSort();
548             if (sort == Type.OBJECT) {
549                 assertTrue(!getBase(type.getInternalName()).equals(JAVA_CLASS_NAME));
550             } else if (sort == Type.ARRAY) {
551                 assertTrue(!getBase(type.getElementType().getInternalName())
552                         .equals(JAVA_CLASS_NAME));
553             } else if (sort == Type.METHOD) {
554                 boolean r = true;
555                 for (Type t : type.getArgumentTypes()) {
556                     r &= testType(t);
557                 }
558                 return r & testType(type.getReturnType());
559             }
560             return true;
561         }
562 
getBase(String className)563         private String getBase(String className) {
564             if (className == null) {
565                 return null;
566             }
567             int pos = className.indexOf('$');
568             if (pos > 0) {
569                 return className.substring(0, pos);
570             }
571             return className;
572         }
573     }
574 }
575