view src/test/java/org/icedrobot/daneel/DexifyingRunner.java @ 133:2b2a1c413c0f

Implemented rewriting of fill-array-data. * rewriter/DexRewriter.java (visitInstrFillArrayData): Implemented. * DexifyingRunner.java: Now also verifies generated code to ease debugging. * rewriter/FillArrayDataTest.java: Enabled and expanded test case.
author Michael Starzinger <michi@complang.tuwien.ac.at>
date Thu, 28 Apr 2011 00:26:59 +0200
parents 73f2f6cbd446
children
line wrap: on
line source

/*
 * Daneel - Dalvik to Java bytecode compiler
 * Copyright (C) 2011  IcedRobot team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This file is subject to the "Classpath" exception:
 *
 * Linking this library statically or dynamically with other modules is
 * making a combined work based on this library.  Thus, the terms and
 * conditions of the GNU General Public License cover the whole
 * combination.
 *
 * As a special exception, the copyright holders of this library give you
 * permission to link this library with independent modules to produce an
 * executable, regardless of the license terms of these independent
 * modules, and to copy and distribute the resulting executable under terms
 * of your choice, provided that you also meet, for each linked independent
 * module, the terms and conditions of the license of that module.  An
 * independent module is a module which is not derived from or based on
 * this library.  If you modify this library, you may extend this exception
 * to your version of the library, but you are not obligated to do so.  If
 * you do not wish to do so, delete this exception statement from your
 * version.
 */

package org.icedrobot.daneel;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.URL;

import org.icedrobot.daneel.dex.DexFile;
import org.icedrobot.daneel.loader.Verifier;
import org.icedrobot.daneel.rewriter.DexRewriter;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

import com.android.dx.command.dexer.Main;
import com.android.dx.command.dexer.Main.Arguments;

/**
 * A JUnit runner implementation which makes sure that test code is pushed
 * through the Daneel stack before running a test case. The test code should be
 * a static inner class of the test case and be named {@code DEXCode} for this
 * runner to recognize it.
 */
public class DexifyingRunner extends BlockJUnit4ClassRunner {

    /**
     * Constructs a new JUnit test runner for the given test class. Keep a
     * constructor with that signature around, so that the JUnit framework can
     * instantiate us for {@link org.junit.runner.RunWith} annotations.
     * 
     * @param testClass The given test class.
     */
    public DexifyingRunner(Class<?> testClass) throws InitializationError {
        super(testClass);
    }

    /**
     * Overridden to add pre-processing of test code within our test classes.
     * This is what triggers the test code to be pushed through Daneel.
     */
    @Override
    protected Statement classBlock(RunNotifier notifier) {
        try {
            rewriteTestCode();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return super.classBlock(notifier);
    }

    /**
     * Performs the steps necessary to push the test code through Daneel.
     */
    private void rewriteTestCode() throws Exception {
        File tmp = File.createTempFile("daneel-dexifier", ".dex");
        tmp.deleteOnExit();

        // Find the original test class file.
        String testClass = getTestClass().getJavaClass().getName() + "$DEXCode";
        String srcName = testClass.replace('.', '/') + ".class";
        URL srcURL = ClassLoader.getSystemResource(srcName);
        if (srcURL == null)
            throw new DaneelException("Unable to find test code.");
        String src = srcURL.getFile();
        if (src == null)
            throw new DaneelException("Unable to convert URL to path.");

        // Convert test code into DEX by running it through the 'dx' tool.
        String[] args = new String[] { "--no-strict", "--output=" + tmp, src };
        Arguments arguments = new Arguments();
        arguments.parse(args);
        int dexerResult = Main.run(arguments);
        if (dexerResult != 0)
            throw new DaneelException("Unable to execute 'dx' tool.");

        // Rewrite test code from DEX back to Java using our rewriter.
        DexFile dex;
        if( !System.getProperty("os.name").contains("Windows") ) {
            dex = DexFile.parse(tmp);
        } else {
            //hack for Windows Java bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4715154
            byte[] data = new byte[(int)tmp.length()];
            DataInputStream dai = new DataInputStream(new FileInputStream(tmp));
            dai.readFully(data);
            dai.close();
            dex = DexFile.parse(data);
        }
        byte[] testCode = DexRewriter.rewrite(testClass, dex);

        // Verify the rewritten test code.
        PrintWriter err = new PrintWriter(System.err);
        Verifier.verify(null, dex, testClass, testCode, err);

        // From this point on we no longer need the DEX file
        if (!tmp.delete())
            throw new DaneelException("Unable to delete DEX file.");

        // Load the rewritten test code using the system class loader.
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Method m = ClassLoader.class.getDeclaredMethod("defineClass",
                String.class, byte[].class, int.class, int.class);
        m.setAccessible(true);
        m.invoke(cl, testClass, testCode, 0, testCode.length);
    }
}