<commit_before>package soot.toDex;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.jf.dexlib.AnnotationDirectoryItem;
import org.jf.dexlib.ClassDataItem;
import org.jf.dexlib.ClassDataItem.EncodedField;
import org.jf.dexlib.ClassDataItem.EncodedMethod;
import org.jf.dexlib.ClassDefItem;
import org.jf.dexlib.CodeItem;
import org.jf.dexlib.CodeItem.EncodedCatchHandler;
import org.jf.dexlib.CodeItem.EncodedTypeAddrPair;
import org.jf.dexlib.CodeItem.TryItem;
import org.jf.dexlib.DebugInfoItem;
import org.jf.dexlib.DexFile;
import org.jf.dexlib.FieldIdItem;
import org.jf.dexlib.MethodIdItem;
import org.jf.dexlib.ProtoIdItem;
import org.jf.dexlib.StringIdItem;
import org.jf.dexlib.TypeIdItem;
import org.jf.dexlib.TypeListItem;
import org.jf.dexlib.Code.Instruction;
import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
import org.jf.dexlib.Util.DebugInfoBuilder;
import org.jf.dexlib.Util.Pair;

import soot.Body;
import soot.CompilationDeathException;
import soot.G;
import soot.PackManager;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.SootMethodRef;
import soot.SourceLocator;
import soot.Trap;
import soot.Type;
import soot.Unit;
import soot.jimple.Stmt;
import soot.jimple.toolkits.scalar.EmptySwitchEliminator;
import soot.options.Options;
import soot.tagkit.LineNumberTag;
import soot.tagkit.SourceFileTag;
import soot.tagkit.Tag;

/**
 * Main entry point for the "dex" output format.<br>
 * <br>
 * Use {@link #add(SootClass)} to add classes that should be printed as dex output and {@link #print()} to finally print the classes.<br>
 * If the printer has found the original APK of an added class (via {@link SourceLocator#dexClassIndex()}),
 * the files in the APK are copied to a new one, replacing it's classes.dex and excluding the signature files.
 * Note that you have to sign and align the APK yourself, with jarsigner and zipalign, respectively.<br>
 * If there is no original APK, the printer just emits a classes.dex.
 * 
 * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/jarsigner.html">jarsigner documentation</a>
 * @see <a href="http://developer.android.com/tools/help/zipalign.html">zipalign documentation</a>
 */
public class DexPrinter {
	
	private static final String CLASSES_DEX = "classes.dex";
	
	private static DexFile dexFile;
	
	private File originalApk;
	
	private static DexAnnotation dexAnnotation;
	
	public DexPrinter() {
		dexFile = new DexFile();
		//dexAnnotation = new DexAnnotation(dexFile);
	}
	
	private void printApk(String outputDir, File originalApk) throws IOException {
		ZipOutputStream outputApk;
		if(Options.v().output_jar()) {
			outputApk = PackManager.v().getJarFile();
			G.v().out.println("Writing APK to: " + Options.v().output_dir());
		} else {
			String outputFileName = outputDir + File.separatorChar + originalApk.getName();
		
			File outputFile = new File(outputFileName);
			if(outputFile.exists()) {
				throw new CompilationDeathException("Output file "+outputFile+" exists. Not overwriting.");
			} 
			outputApk = new ZipOutputStream(new FileOutputStream(outputFile));
			G.v().out.println("Writing APK to: " + outputFileName);
		}
		G.v().out.println("do not forget to sign the .apk file with jarsigner and to align it with zipalign");
		ZipFile original = new ZipFile(originalApk);
		copyAllButClassesDexAndSigFiles(original, outputApk);
		original.close();
		
		// put our classes.dex into the zip archive
		outputApk.putNextEntry(new ZipEntry(CLASSES_DEX));
		writeTo(outputApk);
		outputApk.closeEntry();
		outputApk.close();
	}

	private void copyAllButClassesDexAndSigFiles(ZipFile source, ZipOutputStream destination) throws IOException {
		Enumeration<? extends ZipEntry> sourceEntries = source.entries();
		while (sourceEntries.hasMoreElements()) {
			ZipEntry sourceEntry = sourceEntries.nextElement();
			String sourceEntryName = sourceEntry.getName();
			if (sourceEntryName.equals(CLASSES_DEX) || isSignatureFile(sourceEntryName)) {
				continue;
			}
			// separate ZipEntry avoids compression problems due to encodings
			ZipEntry destinationEntry = new ZipEntry(sourceEntryName);
			// use the same compression method as the original (certain files are stored, not compressed)
			destinationEntry.setMethod(sourceEntry.getMethod());
			// copy other necessary fields for STORE method
			destinationEntry.setSize(sourceEntry.getSize());
			destinationEntry.setCrc(sourceEntry.getCrc());
			// finally craft new entry
			destination.putNextEntry(destinationEntry);
			InputStream zipEntryInput = source.getInputStream(sourceEntry);
			byte[] buffer = new byte[2048];
			int bytesRead = zipEntryInput.read(buffer);
			while (bytesRead > 0) {
				destination.write(buffer, 0, bytesRead);
				bytesRead = zipEntryInput.read(buffer);
			}
		}
	}

	private static boolean isSignatureFile(String fileName) {
		StringBuilder sigFileRegex = new StringBuilder();
		// file name must start with META-INF...
		sigFileRegex.append("META\\-INF");
		// ...followed by a zip file separator...
		sigFileRegex.append('/');
		// ...followed by anything but a zip file separator...
		sigFileRegex.append("[^/]+");
		// ...ending with .SF, .DSA, .RSA or .EC
		sigFileRegex.append("(\\.SF|\\.DSA|\\.RSA|\\.EC)$");
		return fileName.matches(sigFileRegex.toString());
	}

	private void writeTo(OutputStream outputStream) throws IOException {
	    
		dexFile.place();
		
		byte[] outArray = new byte[dexFile.getFileSize()];
		ByteArrayAnnotatedOutput outBytes = new ByteArrayAnnotatedOutput(outArray);
		dexFile.writeTo(outBytes);
		DexFile.calcSignature(outArray);
		DexFile.calcChecksum(outArray);<commit_msg>when the string index is greater than 2^16, the const-string "jumbo" instruction must be used instead of the const-string instruction<commit_after>package soot.toDex;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.jf.dexlib.AnnotationDirectoryItem;
import org.jf.dexlib.ClassDataItem;
import org.jf.dexlib.ClassDataItem.EncodedField;
import org.jf.dexlib.ClassDataItem.EncodedMethod;
import org.jf.dexlib.ClassDefItem;
import org.jf.dexlib.CodeItem;
import org.jf.dexlib.CodeItem.EncodedCatchHandler;
import org.jf.dexlib.CodeItem.EncodedTypeAddrPair;
import org.jf.dexlib.CodeItem.TryItem;
import org.jf.dexlib.DebugInfoItem;
import org.jf.dexlib.DexFile;
import org.jf.dexlib.FieldIdItem;
import org.jf.dexlib.MethodIdItem;
import org.jf.dexlib.ProtoIdItem;
import org.jf.dexlib.StringIdItem;
import org.jf.dexlib.TypeIdItem;
import org.jf.dexlib.TypeListItem;
import org.jf.dexlib.Code.Instruction;
import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
import org.jf.dexlib.Util.DebugInfoBuilder;
import org.jf.dexlib.Util.Pair;

import soot.Body;
import soot.CompilationDeathException;
import soot.G;
import soot.PackManager;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.SootMethodRef;
import soot.SourceLocator;
import soot.Trap;
import soot.Type;
import soot.Unit;
import soot.jimple.Stmt;
import soot.jimple.toolkits.scalar.EmptySwitchEliminator;
import soot.options.Options;
import soot.tagkit.LineNumberTag;
import soot.tagkit.SourceFileTag;
import soot.tagkit.Tag;

/**
 * Main entry point for the "dex" output format.<br>
 * <br>
 * Use {@link #add(SootClass)} to add classes that should be printed as dex output and {@link #print()} to finally print the classes.<br>
 * If the printer has found the original APK of an added class (via {@link SourceLocator#dexClassIndex()}),
 * the files in the APK are copied to a new one, replacing it's classes.dex and excluding the signature files.
 * Note that you have to sign and align the APK yourself, with jarsigner and zipalign, respectively.<br>
 * If there is no original APK, the printer just emits a classes.dex.
 * 
 * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/jarsigner.html">jarsigner documentation</a>
 * @see <a href="http://developer.android.com/tools/help/zipalign.html">zipalign documentation</a>
 */
public class DexPrinter {
	
	private static final String CLASSES_DEX = "classes.dex";
	
	private static DexFile dexFile;
	
	private File originalApk;
	
	private static DexAnnotation dexAnnotation;
	
	public DexPrinter() {
		dexFile = new DexFile();
		//dexAnnotation = new DexAnnotation(dexFile);
	}
	
	private void printApk(String outputDir, File originalApk) throws IOException {
		ZipOutputStream outputApk;
		if(Options.v().output_jar()) {
			outputApk = PackManager.v().getJarFile();
			G.v().out.println("Writing APK to: " + Options.v().output_dir());
		} else {
			String outputFileName = outputDir + File.separatorChar + originalApk.getName();
		
			File outputFile = new File(outputFileName);
			if(outputFile.exists()) {
				throw new CompilationDeathException("Output file "+outputFile+" exists. Not overwriting.");
			} 
			outputApk = new ZipOutputStream(new FileOutputStream(outputFile));
			G.v().out.println("Writing APK to: " + outputFileName);
		}
		G.v().out.println("do not forget to sign the .apk file with jarsigner and to align it with zipalign");
		ZipFile original = new ZipFile(originalApk);
		copyAllButClassesDexAndSigFiles(original, outputApk);
		original.close();
		
		// put our classes.dex into the zip archive
		outputApk.putNextEntry(new ZipEntry(CLASSES_DEX));
		writeTo(outputApk);
		outputApk.closeEntry();
		outputApk.close();
	}

	private void copyAllButClassesDexAndSigFiles(ZipFile source, ZipOutputStream destination) throws IOException {
		Enumeration<? extends ZipEntry> sourceEntries = source.entries();
		while (sourceEntries.hasMoreElements()) {
			ZipEntry sourceEntry = sourceEntries.nextElement();
			String sourceEntryName = sourceEntry.getName();
			if (sourceEntryName.equals(CLASSES_DEX) || isSignatureFile(sourceEntryName)) {
				continue;
			}
			// separate ZipEntry avoids compression problems due to encodings
			ZipEntry destinationEntry = new ZipEntry(sourceEntryName);
			// use the same compression method as the original (certain files are stored, not compressed)
			destinationEntry.setMethod(sourceEntry.getMethod());
			// copy other necessary fields for STORE method
			destinationEntry.setSize(sourceEntry.getSize());
			destinationEntry.setCrc(sourceEntry.getCrc());
			// finally craft new entry
			destination.putNextEntry(destinationEntry);
			InputStream zipEntryInput = source.getInputStream(sourceEntry);
			byte[] buffer = new byte[2048];
			int bytesRead = zipEntryInput.read(buffer);
			while (bytesRead > 0) {
				destination.write(buffer, 0, bytesRead);
				bytesRead = zipEntryInput.read(buffer);
			}
		}
	}

	private static boolean isSignatureFile(String fileName) {
		StringBuilder sigFileRegex = new StringBuilder();
		// file name must start with META-INF...
		sigFileRegex.append("META\\-INF");
		// ...followed by a zip file separator...
		sigFileRegex.append('/');
		// ...followed by anything but a zip file separator...
		sigFileRegex.append("[^/]+");
		// ...ending with .SF, .DSA, .RSA or .EC
		sigFileRegex.append("(\\.SF|\\.DSA|\\.RSA|\\.EC)$");
		return fileName.matches(sigFileRegex.toString());
	}

	private void writeTo(OutputStream outputStream) throws IOException {
	    
		dexFile.place();
		
        boolean fixJumbo = true;
        boolean fixGoto = false;
        for (CodeItem codeItem : dexFile.CodeItemsSection.getItems()) {
            codeItem.fixInstructions(fixJumbo, fixGoto);
        }

        dexFile.place();

		byte[] outArray = new byte[dexFile.getFileSize()];
		ByteArrayAnnotatedOutput outBytes = new ByteArrayAnnotatedOutput(outArray);
		dexFile.writeTo(outBytes);
		DexFile.calcSignature(outArray);
		DexFile.calcChecksum(outArray);
{'fraction_non_alphanumeric': 0.11349791599871754, 'fraction_numerical': 0.0013626162231484451, 'mean_word_length': 4.765711645101663, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7034430', 'n_tokens_mistral': 4214, 'n_tokens_neox': 3809, 'n_words': 957}