package com.francetelecom.rd.stubs.engine; /* * #%L * Matos * $Id:$ * $HeadURL:$ * %% * Copyright (C) 2008 - 2014 Orange SA * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; /** * Code patcher that relocate the code in a sub package. Works for all classes but * String and Object. It is important to choose prefix so that there is no risk of * conflict. * @author Pierre Cregut * */ public class PatchConstantPool { final static int CONSTANT_Class = 7; final static int CONSTANT_Fieldref = 9; final static int CONSTANT_Methodref = 10; final static int CONSTANT_InterfaceMethodref = 11; final static int CONSTANT_String = 8; final static int CONSTANT_Integer = 3; final static int CONSTANT_Float = 4; final static int CONSTANT_Long = 5; final static int CONSTANT_Double = 6; final static int CONSTANT_NameAndType = 12; final static int CONSTANT_Utf8 = 1; final static String [] RESERVED_CLASS = { "java/lang/Object", "java/lang/Class", "java/lang/Enum", "java/lang/String", "java/lang/Throwable", "java/io/Serializable", "java/lang/Comparable", "java/lang/CharSequence", "java/lang/annotation/Annotation","java/lang/annotation/RetentionPolicy", "java/lang/annotation/Retention", "java/lang/annotation/Target","java/lang/annotation/ElementType" }; final static Set<String> RESERVED_IDS_SET = new HashSet<String>(); { for(String name:RESERVED_CLASS) RESERVED_IDS_SET.add(name); } final private DataInputStream is; final private DataOutputStream os; final private ByteArrayOutputStream osb; final private byte [] inputArray; final String packagePrefix; final private byte [] aux = new byte [1024]; final private Pattern pattern = Pattern.compile("L([^;<]*)(;|<)"); final private Pattern antipatternReserved; final private Pattern antipatternHack; private Set <Integer> classNames = new HashSet <Integer>(); private PatchNativeMethod pnm; /** * Constructor. Takes the code of the original method as argument * @param inputArray The code as a byte array. * @throws IOException Should not be raised (bytestreams). */ public PatchConstantPool(String name, byte [] inputArray, String packagePrefix) throws IOException { this.inputArray = inputArray; this.packagePrefix = packagePrefix; is = new DataInputStream (new ByteArrayInputStream (inputArray)); osb = new ByteArrayOutputStream (); os = new DataOutputStream(osb); StringBuilder b = new StringBuilder(); b.append("L").append(packagePrefix).append("/("); boolean first = true; for(String id: RESERVED_CLASS) { if (first) first = false; else b.append("|"); b.append(id); } b.append(")([;<])"); antipatternReserved = Pattern.compile(b.toString()); b = new StringBuilder(); b.append("([.][a-zA-Z0-9]+)"); b.append(packagePrefix); b.append("/"); antipatternHack = Pattern.compile(b.toString()); // antipatternGeneric = Pattern.compile("L" + packagePrefix + "/"); pnm = new PatchNativeMethod(); } private void transfer(int size) throws IOException { is.readFully(aux,0,size); os.write(aux,0,size); } /** * This pass is necessary to locate the UTF8 strings used as pure class names * and the ones that are type specifications. * @return * @throws IOException */ private void prepare() throws IOException { DataInputStream is = new DataInputStream (new ByteArrayInputStream (inputArray)); PatchNativeMethod.forceSkip(is,8); short cppool_size = is.readShort(); pnm.setConstantPoolSize(cppool_size); for(int i=1; i < cppool_size; i++) { byte tag = is.readByte(); switch (tag) { case CONSTANT_Class: int classDef = is.readShort(); classNames.add(classDef); break; case CONSTANT_String: PatchNativeMethod.forceSkip(is,2); break; case CONSTANT_Fieldref: case CONSTANT_Methodref: case CONSTANT_InterfaceMethodref: case CONSTANT_Integer: case CONSTANT_Float: case CONSTANT_NameAndType: PatchNativeMethod.forceSkip(is,4); break; case CONSTANT_Long: case CONSTANT_Double: PatchNativeMethod.forceSkip(is,8); i++; // Warning pool index increases by 2 for long values. break; case CONSTANT_Utf8: String utfString = is.readUTF(); pnm.check(i, utfString); if (utfString.contains(packagePrefix)) throw new RuntimeException("Conflict between the strings in the transformed jar and the chosen prefix"); break; } } pnm.prepare(is); is.close(); } /** * Performs the actual transformation on the bytecode * @return the new byte code array. * @throws IOException */ public byte [] transform() throws IOException { prepare(); transfer(8); short cppool_size = is.readShort(); os.writeShort(cppool_size); for(int i=1; i < cppool_size; i++) { byte tag = is.readByte(); os.writeByte(tag); switch (tag) { case CONSTANT_Class: case CONSTANT_String: transfer(2); break; case CONSTANT_Fieldref: case CONSTANT_Methodref: case CONSTANT_InterfaceMethodref: case CONSTANT_Integer: case CONSTANT_Float: case CONSTANT_NameAndType: transfer(4); break; case CONSTANT_Long: case CONSTANT_Double: transfer(8); i++; break; case CONSTANT_Utf8: transformUTF8(i); break; default: throw new RuntimeException("Unknown tag" + tag); } } if (!pnm.needModification()) { int size; while ((size = is.read(aux)) >= 0) { os.write(aux,0,size); } } else { pnm.addConstants(os); pnm.dumpCode(is, os); } is.close(); os.flush(); byte [] result = osb.toByteArray(); pnm.updatePoolSize(result); return result; } private void transformUTF8(int i) throws IOException { String value = is.readUTF(); String newvalue; if (classNames.contains(i) && value.charAt(0) != '[') { newvalue = (RESERVED_IDS_SET.contains(value)) ? value : packagePrefix + "/" + value; } else { newvalue = pattern.matcher(value).replaceAll("L" + packagePrefix + "/$1$2"); // Do not do it on strings ! ldc complains. newvalue = antipatternReserved.matcher(newvalue).replaceAll("L$1$2"); newvalue = antipatternHack.matcher(newvalue).replaceAll("$1"); } os.writeUTF(newvalue); } }