/*
 * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include <stdlib.h>
#include <string.h>
#include "jni_tools.hpp"
#include "jvmti_tools.hpp"
#include "Injector.hpp"

/* ========================================================================== */

/* Opcode Lengths */
static const u1 opcLengths[] = {
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 3, /*   0- 19 */
    3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*  20- 39 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, /*  40- 59 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*  60- 79 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /*  80- 99 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 100-119 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, /* 120-139 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, /* 140-159 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 0, 0, 1, 1, 1, 1, 1, 1, 3, 3, /* 160-179 */
    3, 3, 3, 3, 3, 5, 5, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3, /* 180-199 */
    5, 5, 1                                                     /* 200-    */
};

static const int GROWTH_FACTOR = 2;
static const char* codeAttributeName = "Code";
static const char* lineNumberAttributeName = "LineNumberTable";
static const char* localVarAttributeName = "LocalVariableTable";
static const char* localVarTypeAttributeName = "LocalVariableTypeTable";
static const char* stackMapAttributeName= "StackMapTable";

static u2 codeAttributeIndex;
static u2 lineNumberAttributeIndex;
static u2 localVarAttributeIndex;
static u2 localVarTypeAttributeIndex;
static u2 stackMapAttributeIndex;

static const u1 SAME_BEGIN = 0;
static const u1 SAME_END = 63;

static const u1 SAME_LOCALS_1_STACK_ITEM_BEGIN = 64;
static const u1 SAME_LOCALS_1_STACK_ITEM_END = 127;

//Tags in the range [128-246] are reserved for future use.

static const u1 SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247;

static const u1 CHOP_BEGIN = 248;
static const u1 CHOP_END = 250;

static const u1 SAME_FRAME_EXTENDED = 251;

static const u1 APPEND_BEGIN = 252;
static const u1 APPEND_END = 254;

static const u1 FULL_FRAME = 255;

static const u1 ITEM_Object = 7;
static const u1 ITEM_Uninitialized = 8;

static u2 stackFrameOffset = 0;

static int mode;
static const u1* orig;
static u1* gen;

static u1* inputPos;
static const u1* endPos;
static u1* genPos;
static u1* markPos;

static char** constantPool;
static u2 constantPoolSize;
static u2 constantPoolCount;

static u1 callBytes[] = { opc_invokestatic, 0, 0 };
static u1 allocBytes[] = { opc_invokestatic, 0, 0 };
static u1 zeroBytes[] = { 0, 0, 0 };

static u4 codeLength;
static u4* map;
static jbyte* widening;

/* ========================================================================== */

extern "C" {

    static u1 get_u1() {
        return *inputPos++;
    }

    static u2 get_u2() {
        u1* p = inputPos;
        inputPos += 2;
        return (u2)p[1] | ((u2)p[0]<<8);
    }

    static u4 get_u4() {
        u1* p = inputPos;
        inputPos += 4;
        return (u4)p[3] | ((u4)p[2]<<8) | ((u4)p[1]<<16) | ((u4)p[0]<<24);
    }

    static void put_u1(u1 v) {
        *genPos++ = v;
    }

    static void put_u2(u2 v) {
        *genPos++ = (u1)(v>>8);
        *genPos++ = (u1)(v & 0xFF);
    }

    static void put_u4(u4 v) {
        *genPos++ = (u1)(v>>24);
        *genPos++ = (u1)(v>>16);
        *genPos++ = (u1)(v>>8);
        *genPos++ = (u1)(v & 0xFF);
    }

    static void set_u4(u1* pos, u4 v) {
        *pos++ = (u1)(v>>24);
        *pos++ = (u1)(v>>16);
        *pos++ = (u1)(v>>8);
        *pos++ = (u1)(v & 0xFF);
    }

    static u1 copy_u1() {
        u1 v = get_u1();
        put_u1(v);
        return v;
    }

    static u2 copy_u2() {
        u2 v = get_u2();
        put_u2(v);
        return v;
    }

    static u4 copy_u4() {
        u4 v = get_u4();
        put_u4(v);
        return v;
    }

    static void copy(int count) {
        memcpy(genPos, inputPos, count);
        inputPos += count;
        genPos += count;
    }

    static void skip(int count) {
        inputPos += count;
    }

    static void get(u1* bytes, int count) {
        memcpy(bytes, inputPos, count);
        inputPos += count;
    }

    static void put(u1* bytes, int count) {
        memcpy(genPos, bytes, count);
        genPos += count;
    }

    static void markLocalPositionStart() {
        markPos = inputPos;
    }

    static u4 localPosition() {
        return (u4) (inputPos - markPos);
    }

    void recallPosition() {
        inputPos = markPos;
    }

    static u4 generatedPosition() {
        return (u4) (genPos - gen);
    }

    static void randomAccessWriteU2(int pos, u2 v) {
        gen[pos]   = (u1)(v>>8);
        gen[pos+1] = (u1)(v & 0xFF);
    }

    static void randomAccessWriteU4(int pos, u4 v) {
        gen[pos]   = (u1)(v>>24);
        gen[pos+1] = (u1)(v>>16);
        gen[pos+2] = (u1)(v>>8);
        gen[pos+3] = (u1)(v & 0xFF);
    }

    static int copyConstantPool(u2 constantPoolCount) {
        u2 i;
        u2 len;
        char* utf8;

        constantPoolSize = constantPoolCount;

        NSK_DISPLAY1("copying ConstantPool: %d\n", constantPoolSize);
        constantPool = (char**) malloc(constantPoolSize * sizeof(char*));
        if (!NSK_VERIFY(constantPool != nullptr)) {
            NSK_COMPLAIN0("out of memory\n");
            return NSK_FALSE;
        }

        memset(constantPool, 0, constantPoolSize * sizeof(char*));

        codeAttributeIndex = 0;
        lineNumberAttributeIndex = 0;
        localVarAttributeIndex = 0;
        localVarTypeAttributeIndex = 0;
        stackMapAttributeIndex = 0;

        for (i = 1; i < constantPoolSize; i++) {
            u1 tag = copy_u1();
            switch (tag) {
            case CONSTANT_Class:
            case CONSTANT_String:
            case CONSTANT_MethodType:
                copy(2);
                break;
            case CONSTANT_MethodHandle:
                copy(3);
                break;
            case CONSTANT_Fieldref:
            case CONSTANT_Methodref:
            case CONSTANT_InterfaceMethodref:
            case CONSTANT_Integer:
            case CONSTANT_Float:
            case CONSTANT_NameAndType:
            case CONSTANT_InvokeDynamic:
                copy(4);
                break;
            case CONSTANT_Long:
            case CONSTANT_Double:
                copy(8);
                i++; /* takes two CP entries */
                break;
            case CONSTANT_Utf8:
                len = copy_u2();
                utf8 = (char*) malloc(len + 1);
                if (!NSK_VERIFY(utf8 != nullptr)) {
                    NSK_COMPLAIN0("out of memory\n");
                    return NSK_FALSE;
                }
                get((u1*) utf8, len);
                utf8[len] = 0;
                constantPool[i] = utf8;
                if (strcmp(utf8, codeAttributeName) == 0) {
                    codeAttributeIndex = i;
                } else if (strcmp(utf8, lineNumberAttributeName) == 0) {
                    lineNumberAttributeIndex = i;
                } else if (strcmp(utf8, localVarAttributeName) == 0) {
                    localVarAttributeIndex = i;
                } else if (strcmp(utf8, localVarTypeAttributeName) == 0) {
                    localVarTypeAttributeIndex = i;
                } else if (strcmp(utf8, stackMapAttributeName) == 0) {
                    stackMapAttributeIndex = i;
                }
                put((u1*) utf8, len);
                break;
            default:
                NSK_COMPLAIN2("%d unexpected constant pool tag: %d\n", i, tag);
                return NSK_FALSE;
            }
        }

        return NSK_TRUE;
    }

    static void freeConstantPool() {
        u2 i;

        for (i = 1; i < constantPoolSize; i++) {
            if (constantPool[i] != nullptr) {
                free(constantPool[i]);
            }
        }

        free(constantPool);
    }

    /* ========================================================================== */

    /* Copy the exception table for this method code */
    static void copyExceptionTable() {
        u2 tableLength;
        u2 i;

        tableLength = copy_u2();
        NSK_DISPLAY1("ExceptionTable length: %d\n", tableLength);
        for (i = tableLength; i > 0; i--) {
            put_u2((u2) map[get_u2()]); /* start_pc */
            put_u2((u2) map[get_u2()]); /* end_pc */
            put_u2((u2) map[get_u2()]); /* handler_pc */
            copy(2);                    /* catch_type */
        }
    }

    /* Copy the line number table for this method code */
    static void copyLineNumberAttr() {
        u2 tableLength;
        u2 i;

        copy(4); /* attr len */

        tableLength = copy_u2();

        NSK_DISPLAY1("LineNumberTable length: %d\n", tableLength);
        for (i = tableLength; i > 0; i--) {
            put_u2((u2) map[get_u2()]); /* start_pc */
            copy(2);                    /* line_number */
        }
    }

    /* Copy the local variable table for this method code */
    static void copyLocalVarAttr() {
        u2 tableLength;
        u2 startPC;
        u2 i;

        copy(4); /* attr len */

        tableLength = copy_u2();

        NSK_DISPLAY1("LocalVariableTable length: %d\n", tableLength);
        for (i = tableLength; i > 0; i--) {
            startPC = get_u2();
            put_u2((u2) map[startPC]);  /* start_pc */
            put_u2((u2) (map[startPC + get_u2()] - map[startPC])); /* length */
            copy(6); /* name_index, descriptor_index, index */
        }
    }

    /* Copy the local variable type table for this method code */
    static void copyLocalVarTypeAttr() {
        u2 tableLength;
        u2 startPC;
        u2 i;

        copy(4); /* attr len */

        tableLength = copy_u2();

        NSK_DISPLAY1("LocalVariableTypeTable length: %d\n", tableLength);
        for (i = tableLength; i > 0; i--) {
            startPC = get_u2();
            put_u2((u2) map[startPC]);  /* start_pc */
            put_u2((u2) (map[startPC + get_u2()] - map[startPC])); /* length */
            copy(6); /* name_index, signature_index, index */
        }
    }

    static u2 calculateOffsetDelta(u2 frameNumber, u2 frameOffsetDelta) {
        u2 oldOffset;
        u2 newOffset;
        if (frameNumber == 0) {
            stackFrameOffset = frameOffsetDelta;
            return (u2) map[stackFrameOffset];
        } else {
            oldOffset = (u2) map[stackFrameOffset];
            stackFrameOffset = stackFrameOffset + frameOffsetDelta + 1;
            newOffset = (u2) map[stackFrameOffset - 1];
            return newOffset - oldOffset;
        }
    }

    static void copyVerificationTypeInfo(u2 count) {
        u2 i;
        u2 offset;
        u1 tag;
        for (i=0; i<count; i++) {
            tag = get_u1();
            put_u1(tag);
            if (tag == ITEM_Object) {
                copy_u2();
            } else if (tag == ITEM_Uninitialized) {
                copy_u2();
                offset = get_u2();
                put_u2((u2)map[offset]);
            }
        }
    }

    static void copyStackMapAttr() {
        u2 number_of_entries;
        u2 i;
        u4 len;
        unsigned int frame_type;
        u2 frameOffsetDelta;
        u2 number_of_stack_items;
        u2 number_of_locals;
        u1* lenPtr = genPos;

        len=copy_u4(); /* attr len */

        number_of_entries = copy_u2();



        for (i=0; i<number_of_entries; i++) {
            frame_type = get_u1();

            if (frame_type <= SAME_END) {
                // same_frame {
                //        u1 frame_type = SAME; /* 0-63 */
                // }

                put_u1(SAME_FRAME_EXTENDED);
                put_u2(calculateOffsetDelta(i, (u2) frame_type));

            } else if ((frame_type >= SAME_LOCALS_1_STACK_ITEM_BEGIN) && (frame_type <= SAME_LOCALS_1_STACK_ITEM_END)) {
                // same_locals_1_stack_item_frame {
                //         u1 frame_type = SAME_LOCALS_1_STACK_ITEM;/* 64-127 */
                //         verification_type_info stack[1];
                // }

                put_u1(SAME_LOCALS_1_STACK_ITEM_EXTENDED);
                put_u2(calculateOffsetDelta(i, (u2) (frame_type-64)));
                copyVerificationTypeInfo(1);

                // Tags in the range [128-246] are reserved for future use.
            } else if (frame_type == SAME_LOCALS_1_STACK_ITEM_EXTENDED) {
                // same_locals_1_stack_item_frame_extended {
                //     u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED; /* 247 */
                //     u2 offset_delta;
                //     verification_type_info stack[1];
                // }

                put_u1(SAME_LOCALS_1_STACK_ITEM_EXTENDED);
                frameOffsetDelta = get_u2();
                put_u2(calculateOffsetDelta(i, frameOffsetDelta));
                copyVerificationTypeInfo(1);

            } else if ((frame_type >= CHOP_BEGIN) && (frame_type <= CHOP_END)) {
                // chop_frame {
                //         u1 frame_type = CHOP; /* 248-250 */
                //         u2 offset_delta;
                // }
                put_u1((u1)frame_type);
                frameOffsetDelta = get_u2();
                put_u2(calculateOffsetDelta(i, frameOffsetDelta));

            } else if (frame_type == SAME_FRAME_EXTENDED) {
                // same_frame_extended {
                //     u1 frame_type = SAME_FRAME_EXTENDED; /* 251 */
                //     u2 offset_delta;
                // }

                put_u1(SAME_FRAME_EXTENDED);
                frameOffsetDelta = get_u2();
                put_u2(calculateOffsetDelta(i, frameOffsetDelta));

            } else if ((frame_type >= APPEND_BEGIN) && (frame_type <= APPEND_END)) {
                // append_frame {
                //     u1 frame_type = APPEND; /* 252-254 */
                //     u2 offset_delta;
                //     verification_type_info locals[frame_type - 251];
                // }

                put_u1((u1)frame_type);
                frameOffsetDelta = get_u2();
                put_u2(calculateOffsetDelta(i, frameOffsetDelta));
                copyVerificationTypeInfo((u1)(frame_type - 251));

            } else if (frame_type == FULL_FRAME) {
                // sfull_frame {
                //    u1 frame_type = FULL_FRAME; /* 255 */
                //    u2 offset_delta;
                //    u2 number_of_locals;
                //    verification_type_info locals[number_of_locals];
                //    u2 number_of_stack_items;
                //    verification_type_info stack[number_of_stack_items];
                // }

                put_u1(FULL_FRAME);
                frameOffsetDelta = get_u2();
                put_u2(calculateOffsetDelta(i, frameOffsetDelta));
                number_of_locals = copy_u2();
                copyVerificationTypeInfo(number_of_locals);
                number_of_stack_items = copy_u2();
                copyVerificationTypeInfo(number_of_stack_items);

            }

        }
        set_u4(lenPtr,(u4)((genPos-lenPtr) - 4));

    }

    /* ========================================================================== */

    static void injectBytes(u4 at, u4 len) {
        u4 i;

        NSK_DISPLAY2("Injecting %d bytes at %d\n", len, at);
        for (i = at; i <= codeLength; i++) {
            map[i] += len;
        }
    }

    static void widen(u4 at, jbyte len) {
        u4 i;
        jbyte delta = len - widening[at];

        NSK_DISPLAY2("Widening to %d bytes at %d\n", len, at);
        /* mark at beginning of instruction */
        widening[at] = len;
        /* inject at end of instruction */
        for (i = localPosition(); i <= codeLength; i++) {
            map[i] += delta;
        }
    }

    /* ========================================================================== */

    /**
     * Walk one instruction writing the transformed instruction.
     */
    static void writeInstruction() {
        u4 pos = localPosition();
        u4 newPos = map[pos];
        u1 opcode = get_u1();

        switch (opcode) {

        case opc_wide:
            put_u1(opcode);
            copy(copy_u1() == opc_iinc ? 4 : 2);
            break;

        case opc_new:
        case opc_newarray:
        case opc_anewarray:
        case opc_multianewarray:
            put_u1(opcode);
            copy(opcLengths[opcode] - 1);
            if (mode == BCI_MODE_ALLOC) {
                put(allocBytes, 3);
            }
            break;

        case opc_jsr_w:
        case opc_goto_w:
            put_u1(opcode);
            put_u4(map[pos + get_u4()] - newPos);
            break;

        case opc_jsr:
        case opc_goto:
        case opc_ifeq:
        case opc_ifge:
        case opc_ifgt:
        case opc_ifle:
        case opc_iflt:
        case opc_ifne:
        case opc_if_icmpeq:
        case opc_if_icmpne:
        case opc_if_icmpge:
        case opc_if_icmpgt:
        case opc_if_icmple:
        case opc_if_icmplt:
        case opc_if_acmpeq:
        case opc_if_acmpne:
        case opc_ifnull:
        case opc_ifnonnull: {
            u1 newOpcode = opcode;
            jbyte widened = widening[pos];
            if (widened == 0) { /* not widened */
                put_u1(opcode);
                put_u2((u2) (map[pos + (jshort) get_u2()] - newPos));
            } else if (widened == 2) { /* wide form */
                if (opcode == opc_jsr) {
                    newOpcode = opc_jsr_w;
                } else if (opcode == opc_jsr) {
                    newOpcode = opc_goto_w;
                } else {
                    NSK_COMPLAIN1("unexpected opcode: %d\n", opcode);
                }
                put_u1(newOpcode);
                put_u4(map[pos + (jshort) get_u2()] - newPos);
            } else if (widened == 5) { /* insert goto_w */
                switch (opcode) {
                case opc_ifeq:
                    newOpcode = opc_ifne;
                    break;
                case opc_ifge:
                    newOpcode = opc_iflt;
                    break;
                case opc_ifgt:
                    newOpcode = opc_ifle;
                    break;
                case opc_ifle:
                    newOpcode = opc_ifgt;
                    break;
                case opc_iflt:
                    newOpcode = opc_ifge;
                    break;
                case opc_ifne:
                    newOpcode = opc_ifeq;
                    break;
                case opc_if_icmpeq:
                    newOpcode = opc_if_icmpne;
                    break;
                case opc_if_icmpne:
                    newOpcode = opc_if_icmpeq;
                    break;
                case opc_if_icmpge:
                    newOpcode = opc_if_icmplt;
                    break;
                case opc_if_icmpgt:
                    newOpcode = opc_if_icmple;
                    break;
                case opc_if_icmple:
                    newOpcode = opc_if_icmpgt;
                    break;
                case opc_if_icmplt:
                    newOpcode = opc_if_icmpge;
                    break;
                case opc_if_acmpeq:
                    newOpcode = opc_if_acmpne;
                    break;
                case opc_if_acmpne:
                    newOpcode = opc_if_acmpeq;
                    break;
                case opc_ifnull:
                    newOpcode = opc_ifnonnull;
                    break;
                case opc_ifnonnull:
                    newOpcode = opc_ifnull;
                    break;
                default:
                    NSK_COMPLAIN1("unexpected opcode: %d\n", opcode);
                    break;
                }
                put_u1(newOpcode);  /* write inverse branch */
                put_u1(3 + 5);      /* beyond if and goto_w */
                put_u1(opc_goto_w); /* add a goto_w */
                put_u4(map[pos + (jshort) get_u2()] - newPos);
            } else {
                NSK_COMPLAIN2("unexpected widening: %d, pos=0x%x\n",
                              widened, pos);
            }
            break;
        }

        case opc_tableswitch: {
            u4 i, low, high;

            put_u1(opcode);

            /* skip old padding */
            skip(((pos+4) & (~3)) - (pos+1));

            /* write new padding */
            put(zeroBytes, ((newPos+4) & (~3)) - (newPos+1));
            put_u4(map[pos + get_u4()] - newPos);

            low = copy_u4();
            high = copy_u4();
            for (i = low; i <= high; i++) {
                put_u4(map[pos + get_u4()] - newPos);
            }

            break;
        }

        case opc_lookupswitch: {
            u4 i, npairs;

            put_u1(opcode);

            /* skip old padding */
            skip(((pos+4) & (~3)) - (pos+1));

            /* write new padding */
            put(zeroBytes, ((newPos+4) & (~3)) - (newPos+1));
            put_u4(map[pos + get_u4()] - newPos);

            npairs = copy_u4();
            for (i = npairs; i > 0; i--) {
                copy_u4();
                put_u4(map[pos + get_u4()] - newPos);
            }

            break;
        }

        default:
            put_u1(opcode);
            copy(opcLengths[opcode] - 1);
            break;
        }

    }

    /* ========================================================================== */

    /**
     * Walk one instruction adjusting for insertions
     */
    static int adjustInstruction() {
        u4 pos = localPosition();
        u4 newPos = map[pos];
        u1 opcode = get_u1();

        switch (opcode) {

        case opc_wide:
            skip(get_u1() == opc_iinc ? 4 : 2);
            break;

        case opc_jsr:
        case opc_goto:
        case opc_ifeq:
        case opc_ifge:
        case opc_ifgt:
        case opc_ifle:
        case opc_iflt:
        case opc_ifne:
        case opc_if_icmpeq:
        case opc_if_icmpne:
        case opc_if_icmpge:
        case opc_if_icmpgt:
        case opc_if_icmple:
        case opc_if_icmplt:
        case opc_if_acmpeq:
        case opc_if_acmpne:
        case opc_ifnull:
        case opc_ifnonnull: {
            jbyte widened = widening[pos];
            if (widened == 0) { /* not yet widened */
                jint delta = (jshort) get_u2();
                u4 target = pos + delta;
                u4 newTarget = map[target];
                jint newDelta = newTarget - newPos;
                if ((newDelta < -32768) || (newDelta > 32767)) {
                    if ((opcode == opc_jsr) || (opcode == opc_goto)) {
                        widen(pos, 2); /* will convert to wide */
                    } else {
                        widen(pos, 5); /* will inject goto_w */
                    }
                    return NSK_FALSE;  /* cause restart */
                }
            }
            break;
        }

        case opc_tableswitch: {
            jbyte widened = widening[pos];
            u4 low;
            jbyte deltaPadding;

            /* skip old padding and default */
            skip(((pos+4) & (~3)) - (pos+1) + 4);
            low = get_u4();
            skip((get_u4() - low + 1) * 4);

            deltaPadding = ((newPos+4) & (~3)) - newPos - ((pos+4) & (~3)) + pos;
            if (widened != deltaPadding) {
                widen(pos, deltaPadding);
                return NSK_FALSE;  /* cause restart */
            }
            break;
        }

        case opc_lookupswitch: {
            jbyte widened = widening[pos];
            jbyte deltaPadding;

            /* skip old padding and default */
            skip(((pos+4) & (~3)) - (pos+1) + 4);
            skip(get_u4() * 8);

            deltaPadding = ((newPos+4) & (~3)) - newPos - ((pos+4) & (~3)) + pos;
            if (widened != deltaPadding) {
                widen(pos, deltaPadding);
                return NSK_FALSE;  /* cause restart */
            }
            break;
        }

        default:
            skip(opcLengths[opcode] - 1);
            break;
        }

        return NSK_TRUE;
    }

    /* ========================================================================== */

    /**
     * Walk one instruction inserting instrumentation at specified instructions
     */
    static void insertAtInstruction() {
        u4 pos = localPosition();
        u1 opcode = get_u1();

        switch (opcode) {

        case opc_wide:
            /* no support for instrumenting wide instructions */
            skip(get_u1() == opc_iinc ? 4 : 2);
            break;

        case opc_new:
        case opc_newarray:
        case opc_anewarray:
        case opc_multianewarray:
            skip(opcLengths[opcode] - 1);
            injectBytes(localPosition(), 3);
            break;

        case opc_tableswitch:
            /* skip 4-byte boundry padding and default */
            skip(((pos+4) & (~3)) - (pos+1) + 4);
            {
                u4 low = get_u4();
                skip((get_u4() - low + 1) * 4);
            }
            break;

        case opc_lookupswitch:
            /* skip 4-byte boundry padding and default */
            skip(((pos+4) & (~3)) - (pos+1) + 4);
            skip(get_u4() * 8);
            break;

        default:
            skip(opcLengths[opcode] - 1);
            break;

        }
    }

    static void adjustOffsets() {
        recallPosition();

        if (mode == BCI_MODE_CALL) {
            /* instrument calls - method entry */
            injectBytes(0, 3);
        }

        if (mode == BCI_MODE_ALLOC) {
            /* instrument allocations */
            while (localPosition() < codeLength) {
                insertAtInstruction();
            }
            recallPosition();
        }

        NSK_DISPLAY0("Searching for adjustments...\n");
        while (localPosition() < codeLength) {
            if (!adjustInstruction()) {
                recallPosition();
                NSK_DISPLAY0("Restarting adjustments after change...\n");
            }
        }

        NSK_DISPLAY0("Writing new code...\n");
        recallPosition();

        if (mode == BCI_MODE_CALL) {
            put(callBytes, 3);
        }

        while (localPosition() < codeLength) {
            writeInstruction();
        }
    }

    /* ========================================================================== */

    static void copyAttr() {
        u4 len;

        copy(2);
        len = copy_u4();
        NSK_DISPLAY1("attr len: %d\n", len);
        copy(len);
    }

    static void copyAttrs(u2 attrCount) {
        u2 i;

        for (i = attrCount; i > 0; i--) {
            copyAttr();
        }
    }

    static void copyFields() {
        u2 count;
        u2 attrCount;
        u2 i;

        count = copy_u2();
        NSK_DISPLAY1("fields count: %d\n", count);
        for (i = count; i > 0; i--) {
            /* access, name, descriptor */
            copy(2 + 2 + 2);
            attrCount = copy_u2();
            NSK_DISPLAY1("field attrCount: %d\n", attrCount);
            copyAttrs(attrCount);
        }
    }

    static void copyAttrForCode() {
        u2 nameIndex = copy_u2();

        /* check for Code attr */
        if (nameIndex == lineNumberAttributeIndex) {
            copyLineNumberAttr();
        } else if (nameIndex == localVarAttributeIndex) {
            copyLocalVarAttr();
        } else if (nameIndex == localVarTypeAttributeIndex) {
            copyLocalVarTypeAttr();
        } else if (nameIndex == stackMapAttributeIndex) {
            copyStackMapAttr();
        } else {
            u4 len = copy_u4();
            NSK_DISPLAY1("code attr len: %d\n", len);
            copy(len);
        }
    }

    static void copyCodeAttr(char* name) {
        u4 attrLengthPos;
        u4 attrLength;
        u4 newAttrLength;
        u4 codeLengthPos;
        u4 newCodeLength;
        u2 attrCount;
        u4 i;

        attrLengthPos = generatedPosition();
        attrLength = copy_u4();

        NSK_DISPLAY2("Code attr found: %s, pos=0x%x\n", name,
                     inputPos - orig - 6);

        /* max_stack, max_locals */
        copy(2 + 2);

        codeLengthPos = generatedPosition();
        codeLength = copy_u4();

        if (codeLength == 0) {
            NSK_COMPLAIN0("code_length must be greater than zero\n");
            return;
        }

        if (mode == BCI_MODE_EMCP) {
            /* copy remainder minus already copied */
            copy(attrLength - 8);
            return;
        }

        markLocalPositionStart();

        map = (u4*) malloc((codeLength + 1) * sizeof(u4));
        for (i = 0; i <= codeLength; i++) {
            map[i] = i;
        }

        widening = (jbyte*) malloc(codeLength + 1);
        memset(widening, 0, codeLength + 1);

        adjustOffsets();

        /* fix up code length */
        newCodeLength = generatedPosition() - (codeLengthPos + 4);
        randomAccessWriteU4(codeLengthPos, newCodeLength);
        NSK_DISPLAY2("code length old: %d, new: %d\n",
                     codeLength, newCodeLength);

        copyExceptionTable();

        attrCount = copy_u2();
        for (i = attrCount; i > 0; i--) {
            copyAttrForCode();
        }

        free(map);
        free(widening);

        /* fix up attr length */
        newAttrLength = generatedPosition() - (attrLengthPos + 4);
        randomAccessWriteU4(attrLengthPos, newAttrLength);
        NSK_DISPLAY2("attr length old: %d, new: %d\n",
                     attrLength, newAttrLength);
    }

    static void copyAttrForMethod(char* name) {
        u2 nameIndex;

        nameIndex = copy_u2();
        if (nameIndex == codeAttributeIndex) {
            copyCodeAttr(name);
        } else {
            u4 len = copy_u4();

            NSK_DISPLAY1("method attr len: %d\n", len);
            copy(len);
        }
    }

    static void copyMethod() {
        u2 accessFlags;
        u2 methodNameIdx;
        char* name;
        u2 attrCount;
        u2 i;

        accessFlags = copy_u2();
        methodNameIdx = copy_u2();
        name = constantPool[methodNameIdx];

        /* descriptor */
        copy(2);

        attrCount = copy_u2();
        NSK_DISPLAY1("method attrCount: %d\n", attrCount);
        for (i = attrCount; i > 0; i--) {
            copyAttrForMethod(name);
        }
    }

    static void copyMethods() {
        u2 count;
        u2 i;

        count = copy_u2();
        NSK_DISPLAY1("methods count: %d\n", count);
        for (i = count; i > 0; i--) {
            copyMethod();
        }
    }

    static u2 writeCPEntryUtf8(const char* str) {
        u2 i;
        u2 len = (u2) strlen(str);
        put_u1(CONSTANT_Utf8);
        put_u2(len);
        for (i = 0; i < len; i++) {
            put_u1(str[i]);
        }
        return constantPoolCount++;
    }

    static u2 writeCPEntryClass(u2 classNameIndex) {
        put_u1(CONSTANT_Class);
        put_u2(classNameIndex);
        return constantPoolCount++;
    }

    static u2 writeCPEntryNameAndType(u2 nameIndex, u2 descrIndex) {
        put_u1(CONSTANT_NameAndType);
        put_u2(nameIndex);
        put_u2(descrIndex);
        return constantPoolCount++;
    }

    static u2 writeCPEntryMethodRef(u2 classIndex, u2 nameAndTypeIndex) {
        put_u1(CONSTANT_Methodref);
        put_u2(classIndex);
        put_u2(nameAndTypeIndex);
        return constantPoolCount++;
    }

    static u2 writeCPEntryFieldRef(u2 classIndex, u2 nameAndTypeIndex) {
        put_u1(CONSTANT_Fieldref);
        put_u2(classIndex);
        put_u2(nameAndTypeIndex);
        return constantPoolCount++;
    }

    static u2 addFieldToConstantPool(u2 classIndex, char* fieldName, char* descr) {
        u2 fieldNameIndex = writeCPEntryUtf8(fieldName);
        u2 descrIndex = writeCPEntryUtf8(descr);
        u2 nameAndTypeIndex = writeCPEntryNameAndType(fieldNameIndex, descrIndex);
        u2 fieldIndex = writeCPEntryFieldRef(classIndex, nameAndTypeIndex);
        return fieldIndex;
    }

    static u2 addMethodToConstantPool(u2 classIndex, const char* methodName, const char* descr) {
        u2 methodNameIndex = writeCPEntryUtf8(methodName);
        u2 descrIndex = writeCPEntryUtf8(descr);
        u2 nameAndTypeIndex = writeCPEntryNameAndType(methodNameIndex, descrIndex);
        u2 methodIndex = writeCPEntryMethodRef(classIndex, nameAndTypeIndex);
        return methodIndex;
    }

    static u2 addClassToConstantPool(const char* className) {
        u2 classNameIndex = writeCPEntryUtf8(className);
        u2 classIndex = writeCPEntryClass(classNameIndex);
        return classIndex;
    }

    /* ========================================================================== */

    int Inject(const u1* old_bytes, const jint old_length,
               u1** new_bytes, jint* new_length, int bci_mode) {
        u4 constantPoolCountPos;
        u2 profiler;
        u2 interfaceCount;
        u2 attrCount;

        //printf("inject\n");
        NSK_DISPLAY3("Injecting bytecodes: mode=%d, bytes=0x%p, len=%d\n",
                     bci_mode, old_bytes, old_length);

        mode = bci_mode;
        orig = old_bytes;
        inputPos = (u1*) orig;
        endPos = orig + old_length;
        gen = (u1*) malloc(old_length * GROWTH_FACTOR);
        if (!NSK_VERIFY(gen != nullptr)) {
            NSK_COMPLAIN0("out of memory\n");
            return NSK_FALSE;
        }

        genPos = gen;

        /* magic + minor/major version */
        copy(4 + 2 + 2);

        constantPoolCountPos = generatedPosition();
        constantPoolCount = copy_u2();

        /* copy old constant pool */
        if (!copyConstantPool(constantPoolCount)) {
            return NSK_FALSE;
        }
        NSK_DISPLAY1("ConstantPool expanded from: %d\n", constantPoolCount);

        profiler = addClassToConstantPool("nsk/share/jvmti/ProfileCollector");

        if (mode == BCI_MODE_ALLOC) {
            u2 allocTracker =
                addMethodToConstantPool(profiler, "allocTracker", "()V");
            allocBytes[1] = (u1) (allocTracker >> 8);
            allocBytes[2] = (u1) (allocTracker & 0xFF);
        }

        if (mode == BCI_MODE_CALL) {
            u2 callTracker =
                addMethodToConstantPool(profiler, "callTracker", "()V");
            callBytes[1] = (u1) (callTracker >> 8);
            callBytes[2] = (u1) (callTracker & 0xFF);
        }

        /* access, this, super */
        copy(2 + 2 + 2);

        interfaceCount = copy_u2();
        NSK_DISPLAY1("interfaceCount: %d\n", interfaceCount);
        copy(interfaceCount * 2);

        copyFields();
        copyMethods();

        attrCount = copy_u2();
        NSK_DISPLAY1("class attrCount: %d\n", attrCount);
        copyAttrs(attrCount);

        randomAccessWriteU2(constantPoolCountPos, constantPoolCount);
        NSK_DISPLAY1("New constant pool size: %d\n", constantPoolCount);

        *new_length = (jint) (genPos - gen);
        *new_bytes = (u1*) realloc(gen, *new_length);

        freeConstantPool();

        return NSK_TRUE;
    }

    /* ========================================================================== */

}
