/* * Copyright 2023 Volker Berlin (i-net software) * * 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. */ package de.inetsoftware.jwebassembly.module; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.Nonnull; import de.inetsoftware.classparser.FieldInfo; import de.inetsoftware.jwebassembly.WasmException; import de.inetsoftware.jwebassembly.module.StackInspector.StackValue; import de.inetsoftware.jwebassembly.module.WasmInstruction.Type; import de.inetsoftware.jwebassembly.wasm.AnyType; import de.inetsoftware.jwebassembly.wasm.NamedStorageType; import de.inetsoftware.jwebassembly.wasm.ValueType; import de.inetsoftware.jwebassembly.wasm.VariableOperator; /** * Replace Unsafe operations with simpler WASM operations which does not need reflections. * * In Java a typical Unsafe code look like: * *
 * 
 * private static final Unsafe UNSAFE = Unsafe.getUnsafe();
 * private static final long FIELD_OFFSET = UNSAFE.objectFieldOffset(Foobar.class.getDeclaredField("field"));
 * 
 * ...
 * 
 * UNSAFE.compareAndSwapInt(this, FIELD_OFFSET, expect, update);
 * 
 * 
* * Because WASM does not support reflection the native code of UNSAFE can't simple replaced. That this manager convert * this to the follow pseudo code in WASM: * *
 * 
 * Foobar..compareAndSwapInt(this, FIELD_OFFSET, expect, update);
 * 
 * ...
 * 
 * boolean .compareAndSwapInt(Object obj, long fieldOffset, int expect, int update ) {
 *     if( obj.field == expect ) {
 *         obj.field = update;
 *         return true;
 *     }
 *     return false;
 * }
 * 
 * 
* * @author Volker Berlin */ class UnsafeManager { /** Unsafe class bane in Java 8 */ static final String UNSAFE_8 = "sun/misc/Unsafe"; /** Unsafe class bane in Java 11 */ static final String UNSAFE_11 = "jdk/internal/misc/Unsafe"; /** Wrapper for Unsafe */ static final String FIELDUPDATER = "java/util/concurrent/atomic/AtomicReferenceFieldUpdater"; /** VARHANDLE as modern replacement of Unsafe */ static final String VARHANDLE = "java/lang/invoke/VarHandle"; @Nonnull private final FunctionManager functions; @Nonnull private final TypeManager types; @Nonnull private final ClassFileLoader classFileLoader; private final HashMap unsafes = new HashMap<>(); /** * Create an instance of the manager * * @param options * compiler option/properties * @param classFileLoader * for loading the class files */ UnsafeManager( @Nonnull WasmOptions options, @Nonnull ClassFileLoader classFileLoader ) { this.functions = options.functions; this.types = options.types; this.classFileLoader = classFileLoader; } /** * Replace any Unsafe API call with direct field access. * * @param instructions * the instruction list of a function/method * @throws IOException * If any I/O error occur */ void replaceUnsafe( @Nonnull List instructions ) throws IOException { // search for Unsafe function calls for( int i = 0; i < instructions.size(); i++ ) { WasmInstruction instr = instructions.get( i ); switch( instr.getType() ) { case CallVirtual: case Call: WasmCallInstruction callInst = (WasmCallInstruction)instr; switch( callInst.getFunctionName().className ) { case UNSAFE_8: case UNSAFE_11: case FIELDUPDATER: patch( instructions, i, callInst ); break; case VARHANDLE: patchVarHandle( instructions, i, callInst ); break; } break; default: } } } /** * Patch in the instruction list an Unsafe method call. It does not change the count of instructions. * * @param instructions * the instruction list of a function/method * @param idx * the index in the instructions * @param callInst * the method call to Unsafe * @throws IOException * If any I/O error occur */ private void patch( List instructions, int idx, WasmCallInstruction callInst ) throws IOException { FunctionName name = callInst.getFunctionName(); switch( name.signatureName ) { case "sun/misc/Unsafe.getUnsafe()Lsun/misc/Unsafe;": case "jdk/internal/misc/Unsafe.getUnsafe()Ljdk/internal/misc/Unsafe;": patch_getUnsafe( instructions, idx ); break; case "sun/misc/Unsafe.objectFieldOffset(Ljava/lang/reflect/Field;)J": case "jdk/internal/misc/Unsafe.objectFieldOffset(Ljava/lang/reflect/Field;)J": patch_objectFieldOffset_Java8( instructions, idx, callInst ); break; case "jdk/internal/misc/Unsafe.objectFieldOffset(Ljava/lang/Class;Ljava/lang/String;)J": patch_objectFieldOffset_Java11( instructions, idx, callInst, false ); break; case "java/util/concurrent/atomic/AtomicReferenceFieldUpdater.newUpdater(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;": patch_objectFieldOffset_Java11( instructions, idx, callInst, true ); break; case "sun/misc/Unsafe.arrayBaseOffset(Ljava/lang/Class;)I": case "jdk/internal/misc/Unsafe.arrayBaseOffset(Ljava/lang/Class;)I": patch_arrayBaseOffset( instructions, idx, callInst ); break; case "sun/misc/Unsafe.arrayIndexScale(Ljava/lang/Class;)I": case "jdk/internal/misc/Unsafe.arrayIndexScale(Ljava/lang/Class;)I": patch_arrayIndexScale( instructions, idx, callInst ); break; case "sun/misc/Unsafe.getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object;": case "sun/misc/Unsafe.getInt(Ljava/lang/Object;J)I": case "sun/misc/Unsafe.getLong(Ljava/lang/Object;J)J": case "jdk/internal/misc/Unsafe.getInt(Ljava/lang/Object;J)I": case "jdk/internal/misc/Unsafe.getLong(Ljava/lang/Object;J)J": patchFieldFunction( instructions, idx, callInst, name, 1 ); break; case "sun/misc/Unsafe.getAndAddInt(Ljava/lang/Object;JI)I": case "sun/misc/Unsafe.getAndSetInt(Ljava/lang/Object;JI)I": case "sun/misc/Unsafe.putOrderedInt(Ljava/lang/Object;JI)V": case "sun/misc/Unsafe.putInt(Ljava/lang/Object;JI)V": case "sun/misc/Unsafe.getAndAddLong(Ljava/lang/Object;JJ)J": case "sun/misc/Unsafe.getAndSetLong(Ljava/lang/Object;JJ)J": case "sun/misc/Unsafe.putOrderedLong(Ljava/lang/Object;JJ)V": case "sun/misc/Unsafe.putLong(Ljava/lang/Object;JJ)V": case "sun/misc/Unsafe.putOrderedObject(Ljava/lang/Object;JLjava/lang/Object;)V": case "sun/misc/Unsafe.putObjectVolatile(Ljava/lang/Object;JLjava/lang/Object;)V": case "sun/misc/Unsafe.putObject(Ljava/lang/Object;JLjava/lang/Object;)V": case "sun/misc/Unsafe.getAndSetObject(Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object;": case "jdk/internal/misc/Unsafe.getAndAddInt(Ljava/lang/Object;JI)I": case "jdk/internal/misc/Unsafe.getAndSetInt(Ljava/lang/Object;JI)I": case "jdk/internal/misc/Unsafe.putIntRelease(Ljava/lang/Object;JI)V": case "jdk/internal/misc/Unsafe.putInt(Ljava/lang/Object;JI)V": case "jdk/internal/misc/Unsafe.getAndAddLong(Ljava/lang/Object;JJ)J": case "jdk/internal/misc/Unsafe.getAndSetLong(Ljava/lang/Object;JJ)J": case "jdk/internal/misc/Unsafe.putLongRelease(Ljava/lang/Object;JJ)V": case "jdk/internal/misc/Unsafe.putLongVolatile(Ljava/lang/Object;JJ)V": case "jdk/internal/misc/Unsafe.putLong(Ljava/lang/Object;JJ)V": case "jdk/internal/misc/Unsafe.putObject(Ljava/lang/Object;JLjava/lang/Object;)V": case "jdk/internal/misc/Unsafe.getObjectAcquire(Ljava/lang/Object;J)Ljava/lang/Object;": case "jdk/internal/misc/Unsafe.putObjectRelease(Ljava/lang/Object;JLjava/lang/Object;)V": patchFieldFunction( instructions, idx, callInst, name, 2 ); break; case "sun/misc/Unsafe.compareAndSwapInt(Ljava/lang/Object;JII)Z": case "sun/misc/Unsafe.compareAndSwapLong(Ljava/lang/Object;JJJ)Z": case "sun/misc/Unsafe.compareAndSwapObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z": case "jdk/internal/misc/Unsafe.compareAndSetInt(Ljava/lang/Object;JII)Z": case "jdk/internal/misc/Unsafe.compareAndSetLong(Ljava/lang/Object;JJJ)Z": case "jdk/internal/misc/Unsafe.compareAndSetObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z": patchFieldFunction( instructions, idx, callInst, name, 3 ); break; case "java/util/concurrent/atomic/AtomicReferenceFieldUpdater.compareAndSet(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z": patchFieldFunction( instructions, idx, callInst, name, 4 ); break; case "jdk/internal/misc/Unsafe.getLongUnaligned(Ljava/lang/Object;J)J": case "jdk/internal/misc/Unsafe.getIntUnaligned(Ljava/lang/Object;J)I": case "jdk/internal/misc/Unsafe.getCharUnaligned(Ljava/lang/Object;JZ)C": case "jdk/internal/misc/Unsafe.getIntUnaligned(Ljava/lang/Object;JZ)I": patch_getLongUnaligned( instructions, idx, callInst, name ); break; case "jdk/internal/misc/Unsafe.isBigEndian()Z": patch_isBigEndian( instructions, idx, callInst ); break; case "jdk/internal/misc/Unsafe.shouldBeInitialized(Ljava/lang/Class;)Z": replaceWithConstNumber( instructions, idx, callInst, 2, 0 ); break; case "jdk/internal/misc/Unsafe.storeFence()V": remove( instructions, idx, callInst, 1 ); break; case "jdk/internal/misc/Unsafe.ensureClassInitialized(Ljava/lang/Class;)V": case "jdk/internal/misc/Unsafe.unpark(Ljava/lang/Object;)V": case "sun/misc/Unsafe.unpark(Ljava/lang/Object;)V": remove( instructions, idx, callInst, 2 ); break; case "sun/misc/Unsafe.park(ZJ)V": case "jdk/internal/misc/Unsafe.park(ZJ)V": remove( instructions, idx, callInst, 3 ); break; default: throw new WasmException( "Unsupported Unsafe method: " + name.signatureName, -1 ); } } /** * Replace a call to Unsafe.getUnsafe() with a NOP operation. * * @param instructions * the instruction list of a function/method * @param idx * the index in the instructions */ private void patch_getUnsafe( List instructions, int idx ) { WasmInstruction instr = instructions.get( idx + 1 ); int to = idx + (instr.getType() == Type.Global ? 2 : 1); nop( instructions, idx, to ); } /** * Find the field on which the offset is assign: long FIELD_OFFSET = UNSAFE.objectFieldOffset(... * * @param instructions * the instruction list of a function/method * @param idx * the index in the instructions * @return the state */ @Nonnull private UnsafeState findUnsafeState( List instructions, int idx ) { // find the field on which the offset is assign: long FIELD_OFFSET = UNSAFE.objectFieldOffset(... WasmInstruction instr; idx++; INSTR: do { instr = instructions.get( idx ); switch( instr.getType() ) { case Convert: idx++; continue INSTR; case Global: break; case Jump: int pos = ((JumpInstruction)instr).getJumpPosition(); for( idx++; idx < instructions.size(); idx++) { instr = instructions.get( idx ); if( instr.getCodePosition() >= pos ) { break; } } continue INSTR; default: throw new WasmException( "Unsupported assign operation for Unsafe field offset: " + instr.getType(), -1 ); } break; } while( true ); FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName(); UnsafeState state = unsafes.get( fieldNameWithOffset ); if( state == null ) { unsafes.put( fieldNameWithOffset, state = new UnsafeState() ); } return state; } /** * Get the class name from the stack value. It is searching a WasmConstClassInstruction that produce the value of * the stack value. * * @param instructions * the instruction list of a function/method * @param stackValue * the stack value (instruction and position that produce an stack value) * @return the class name like: java/lang/String */ @Nonnull private static String getClassConst( List instructions, StackValue stackValue ) { WasmInstruction instr = stackValue.instr; switch( instr.getType() ) { case Local: int slot = ((WasmLocalInstruction)instr).getSlot(); for( int i = stackValue.idx - 1; i >= 0; i-- ) { instr = instructions.get( i ); if( instr.getType() == Type.Local ) { WasmLocalInstruction loadInstr = (WasmLocalInstruction)instr; if( loadInstr.getSlot() == slot && loadInstr.getOperator() == VariableOperator.set ) { stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, i ), 1, instr.getCodePosition() ); instr = stackValue.instr; break; } } } break; default: } return ((WasmConstClassInstruction)instr).getValue(); } /** * Patch a method call to Unsafe.objectFieldOffset() and find the parameter for other patch operations. * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe * @throws IOException * If any I/O error occur */ private void patch_objectFieldOffset_Java8( List instructions, int idx, WasmCallInstruction callInst ) throws IOException { UnsafeState state = findUnsafeState( instructions, idx ); // objectFieldOffset() has 2 parameters THIS(Unsafe) and a Field List paramInstructions = instructions.subList( 0, idx ); int from = StackInspector.findInstructionThatPushValue( paramInstructions, 2, callInst.getCodePosition() ).idx; StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 1, callInst.getCodePosition() ); WasmInstruction instr = stackValue.instr; WasmCallInstruction fieldInst = (WasmCallInstruction)instr; FunctionName fieldFuncName = fieldInst.getFunctionName(); switch( fieldFuncName.signatureName ) { case "java/lang/Class.getDeclaredField(Ljava/lang/String;)Ljava/lang/reflect/Field;": stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, stackValue.idx ), 1, fieldInst.getCodePosition() ); state.fieldName = ((WasmConstStringInstruction)stackValue.instr).getValue(); // find the class value on which getDeclaredField is called stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, stackValue.idx ), 1, fieldInst.getCodePosition() ); state.typeName = getClassConst( instructions, stackValue ); break; default: throw new WasmException( "Unsupported Unsafe method to get target field: " + fieldFuncName.signatureName, -1 ); } useFieldName( state ); nop( instructions, from, idx + 2 ); } /** * Patch a method call to Unsafe.objectFieldOffset() and find the parameter for other patch operations. * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe * @param isAtomicReferenceFieldUpdater * true, if is AtomicReferenceFieldUpdater * @throws IOException * If any I/O error occur */ private void patch_objectFieldOffset_Java11( List instructions, int idx, WasmCallInstruction callInst, boolean isAtomicReferenceFieldUpdater ) throws IOException { UnsafeState state = findUnsafeState( instructions, idx ); // objectFieldOffset() has 3 parameters THIS(Unsafe), class and the fieldname List paramInstructions = instructions.subList( 0, idx ); int from = StackInspector.findInstructionThatPushValue( paramInstructions, 3, callInst.getCodePosition() ).idx; StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 1, callInst.getCodePosition() ); state.fieldName = ((WasmConstStringInstruction)stackValue.instr).getValue(); // find the class value on which getDeclaredField is called int classParamIdx = isAtomicReferenceFieldUpdater ? 3 : 2; stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, classParamIdx, callInst.getCodePosition() ); state.typeName = getClassConst( instructions, stackValue ); useFieldName( state ); nop( instructions, from, idx + 2 ); } /** * Patch a method call to Unsafe.arrayBaseOffset() and find the parameter for other patch operations. * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe */ private void patch_arrayBaseOffset( List instructions, int idx, WasmCallInstruction callInst ) { UnsafeState state = findUnsafeState( instructions, idx ); // objectFieldOffset() has 2 parameters THIS(Unsafe) and a Class from an array List paramInstructions = instructions.subList( 0, idx ); int from = StackInspector.findInstructionThatPushValue( paramInstructions, 2, callInst.getCodePosition() ).idx; StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 1, callInst.getCodePosition() ); state.typeName = getClassConst( instructions, stackValue ); nop( instructions, from, idx ); // we put the constant value 0 on the stack, we does not need array base offset in WASM instructions.set( idx, new WasmConstNumberInstruction( 0, callInst.getCodePosition(), callInst.getLineNumber() ) ); } /** * Patch method call to Unsafe.arrayIndexScale() * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe */ private void patch_arrayIndexScale( List instructions, int idx, WasmCallInstruction callInst ) { int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 2, callInst.getCodePosition() ).idx; nop( instructions, from, idx ); // we put the constant value 1 on the stack because we does not want shift array positions instructions.set( idx, new WasmConstNumberInstruction( 1, callInst.getCodePosition(), callInst.getLineNumber() ) ); } /** * Mark the field as used * * @param state * the state * @throws IOException * If any I/O error occur */ private void useFieldName( @Nonnull UnsafeState state ) throws IOException { FieldInfo fieldInfo = classFileLoader.get( state.typeName ).getField( state.fieldName ); NamedStorageType fieldName = new NamedStorageType( state.typeName, fieldInfo, types ); types.valueOf( state.typeName ).useFieldName( fieldName ); } /** * Patch an unsafe function that access a field * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe * @param name * the calling function * @param fieldNameParam * the function parameter on the stack with the field offset on the stack. This must be a long (Java signature "J") for Unsafe. This is the parameter count from right. */ private void patchFieldFunction( List instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) { StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() ); WasmInstruction instr = stackValue.instr; Set fieldNames; FunctionName fieldNameWithOffset = null; if( instr.getType() == Type.Global ) { fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName(); fieldNames = Collections.singleton( fieldNameWithOffset ); } else { // java.util.concurrent.ConcurrentHashMap.tabAt() calculate a value with the field fieldNames = new HashSet<>(); int pos2 = stackValue.idx; stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam + 1, callInst.getCodePosition() ); int i = stackValue.idx; for( ; i < pos2; i++ ) { instr = instructions.get( i ); if( instr.getType() != Type.Global ) { continue; } fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName(); fieldNames.add( fieldNameWithOffset ); } } WatCodeSyntheticFunctionName func = new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + fieldNameWithOffset.methodName + '.' + name.methodName, name.signature, "", (AnyType[])null ) { @Override protected String getCode() { UnsafeState state = null; for(FunctionName fieldNameWithOffset : fieldNames ) { state = unsafes.get( fieldNameWithOffset ); if( state != null ) { break; } } if( state == null ) { if( functions.isFinish() ) { throw new RuntimeException( this.fullName + name.signature ); } // we are in the scan phase. The static code was not scanned yet. return ""; } AnyType[] paramTypes = callInst.getPopValueTypes(); switch( name.methodName ) { case "compareAndSwapInt": case "compareAndSetInt": case "compareAndSwapLong": case "compareAndSetLong": case "compareAndSwapObject": case "compareAndSetObject": case "compareAndSet": // AtomicReferenceFieldUpdater AnyType type = paramTypes[3]; if( type.isRefType() ) { type = ValueType.ref; } if( state.fieldName != null ) { // field access int paramOffset = "java/util/concurrent/atomic/AtomicReferenceFieldUpdater".equals( name.className ) ? -1 : 0; return "local.get 1" // THIS + " struct.get " + state.typeName + ' ' + state.fieldName // + " local.get " + (3 + paramOffset) // expected + " " + type + ".eq" // + " if" // + " local.get 1" // THIS + " local.get " + (4 + paramOffset) // update + " struct.set " + state.typeName + ' ' + state.fieldName // + " i32.const 1" // + " return" // + " end" // + " i32.const 1" // + " return"; } else { // array access return "local.get 1" // THIS + " local.get 2" // the array index + " i32.wrap_i64" // long -> int + " array.get " + state.typeName // + " local.get 3 " // expected + " " + type + ".eq" // + " if" // + " local.get 1" // THIS + " local.get 2" // the array index + " i32.wrap_i64" // long -> int + " local.get 4 " // update + " array.set " + state.typeName // + " i32.const 1" // + " return" // + " end" // + " i32.const 1" // + " return"; } case "getAndAddInt": case "getAndAddLong": return "local.get 1" // THIS + " local.get 1" // THIS + " struct.get " + state.typeName + ' ' + state.fieldName // + " local.tee 4" // temp + " local.get 3 " // delta + paramTypes[3] + ".add" // + " struct.set " + state.typeName + ' ' + state.fieldName // + " local.get 4" // temp + " return"; case "getAndSetInt": case "getAndSetLong": case "getAndSetObject": return "local.get 1" // THIS + " struct.get " + state.typeName + ' ' + state.fieldName // + " local.get 1" // THIS + " local.get 3" // newValue + " struct.set " + state.typeName + ' ' + state.fieldName // + " return"; case "putOrderedInt": case "putInt": case "putOrderedLong": case "putLong": case "putLongVolatile": case "putOrderedObject": case "putObjectVolatile": case "putObject": case "putObjectRelease": if( state.fieldName != null ) { // field access return "local.get 1" // THIS + " local.get 3" // x + " struct.set " + state.typeName + ' ' + state.fieldName; } else { // array access return "local.get 1" // THIS + " local.get 2" // the array index + " i32.wrap_i64" // long -> int + " local.get 3" // x + " array.set " + state.typeName; } case "getInt": case "getLong": return "local.get 1" // THIS + " struct.get " + state.typeName + ' ' + state.fieldName // + " return"; case "getObjectVolatile": return "local.get 1" // array + " local.get 2" // the array index + " i32.wrap_i64" // long -> int + " array.get " + state.typeName + " return"; } throw new RuntimeException( name.signatureName ); } }; boolean needThisParameter = true; functions.markAsNeeded( func, needThisParameter ); // original function has an THIS parameter of the Unsafe instance, we need to consume it WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), needThisParameter ); instructions.set( idx, call ); // a virtual method call has also a DUP of this because we need for virtual method dispatch the parameter 2 times. for( int i = idx; i >= 0; i-- ) { instr = instructions.get( i ); if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) { nop( instructions, i, i + 1 ); break; } } } /** * Patch an unsafe function that access a field * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe */ private void patch_getLongUnaligned( List instructions, int idx, final WasmCallInstruction callInst, FunctionName name ) { WatCodeSyntheticFunctionName func = new WatCodeSyntheticFunctionName( "", name.methodName, name.signature, "unreachable", (AnyType[])null ); functions.markAsNeeded( func, false ); WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), false ); instructions.set( idx, call ); } /** * Patch an unsafe function that access a field * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe */ private void patch_isBigEndian( List instructions, int idx, final WasmCallInstruction callInst ) { // int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 1, callInst.getCodePosition() ).idx; // // nop( instructions, from, idx ); // on x86 use little endian instructions.set( idx, new WasmConstNumberInstruction( 0, callInst.getCodePosition(), callInst.getLineNumber() ) ); } /** * Patch an unsafe function that access a field * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe */ private void replaceWithConstNumber( List instructions, int idx, final WasmCallInstruction callInst, int paramCount, int number ) { int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 1, callInst.getCodePosition() ).idx; nop( instructions, from, idx ); // on x86 use little endian instructions.set( idx, new WasmConstNumberInstruction( number, callInst.getCodePosition(), callInst.getLineNumber() ) ); } /** * Patch an unsafe function that access a field * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe */ private void remove( List instructions, int idx, final WasmCallInstruction callInst, int paramCount ) { int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), paramCount, callInst.getCodePosition() ).idx; nop( instructions, from, idx + 1 ); } /** * Replace the instructions with NOP operations * * @param instructions * the instruction list * @param from * starting index * @param to * end index */ private void nop( List instructions, int from, int to ) { for( int i = from; i < to; i++ ) { WasmInstruction instr = instructions.get( i ); instructions.set( i, new WasmNopInstruction( instr.getCodePosition(), instr.getLineNumber() ) ); } } private void patchVarHandle( List instructions, int idx, WasmCallInstruction callInst ) { FunctionName name = callInst.getFunctionName(); switch( name.methodName ) { case "getAndSet": patchVarHandleFieldFunction( instructions, idx, callInst, name, 3 ); break; } } /** * Patch an unsafe function that access a field * * @param instructions * the instruction list * @param idx * the index in the instructions * @param callInst * the method call to Unsafe * @param name * the calling function * @param fieldNameParam * the function parameter with the field offset on the stack. This must be a long (Java signature "J") for Unsafe. */ private void patchVarHandleFieldFunction( List instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) { StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() ); FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)stackValue.instr).getFieldName(); WatCodeSyntheticFunctionName func = new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + name.methodName, name.signature, "", (AnyType[])null ) { @Override protected String getCode() { UnsafeState state = unsafes.get( fieldNameWithOffset ); if( state == null ) { // we are in the scan phase. The static code was not scanned yet. return ""; } AnyType[] paramTypes = callInst.getPopValueTypes(); switch( name.methodName ) { case "getAndSet": return "local.get 1" // THIS + " struct.get " + state.typeName + ' ' + state.fieldName // + " local.get 1" // THIS + " local.get 2" // newValue + " struct.set " + state.typeName + ' ' + state.fieldName // + " return"; } throw new RuntimeException( name.signatureName ); } }; functions.markAsNeeded( func, false ); WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), false ); instructions.set( idx, call ); // a virtual method call has also a DUP of this because we need for virtual method dispatch the parameter 2 times. for( int i = idx; i >= 0; i-- ) { WasmInstruction instr = instructions.get( i ); if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) { nop( instructions, i, i + 1 ); break; } } } /** * Hold the state from declaring of Unsafe address */ static class UnsafeState { String fieldName; String typeName; } }