• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

i-net-software / JWebAssembly / 534

pending completion
534

push

travis-ci-com

Horcrux7
replace static constructor für MethodHandles.

1 of 1 new or added line in 1 file covered. (100.0%)

5933 of 6826 relevant lines covered (86.92%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.69
/src/de/inetsoftware/jwebassembly/module/UnsafeManager.java
1
/*
2
 * Copyright 2023 Volker Berlin (i-net software)
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package de.inetsoftware.jwebassembly.module;
17

18
import java.io.IOException;
19
import java.util.Collections;
20
import java.util.HashMap;
21
import java.util.HashSet;
22
import java.util.List;
23
import java.util.Set;
24

25
import javax.annotation.Nonnull;
26

27
import de.inetsoftware.classparser.FieldInfo;
28
import de.inetsoftware.jwebassembly.WasmException;
29
import de.inetsoftware.jwebassembly.module.StackInspector.StackValue;
30
import de.inetsoftware.jwebassembly.module.WasmInstruction.Type;
31
import de.inetsoftware.jwebassembly.wasm.AnyType;
32
import de.inetsoftware.jwebassembly.wasm.NamedStorageType;
33
import de.inetsoftware.jwebassembly.wasm.ValueType;
34
import de.inetsoftware.jwebassembly.wasm.VariableOperator;
35

36
/**
37
 * Replace Unsafe operations with simpler WASM operations which does not need reflections.
38
 * 
39
 * In Java a typical Unsafe code look like:
40
 * 
41
 * <pre>
42
 * <code>
43
 * private static final Unsafe UNSAFE = Unsafe.getUnsafe();
44
 * private static final long FIELD_OFFSET = UNSAFE.objectFieldOffset(Foobar.class.getDeclaredField("field"));
45
 * 
46
 * ...
47
 * 
48
 * UNSAFE.compareAndSwapInt(this, FIELD_OFFSET, expect, update);
49
 * </code>
50
 * </pre>
51
 * 
52
 * Because WASM does not support reflection the native code of UNSAFE can't simple replaced. That this manager convert
53
 * this to the follow pseudo code in WASM:
54
 * 
55
 * <pre>
56
 * <code>
57
 * Foobar..compareAndSwapInt(this, FIELD_OFFSET, expect, update);
58
 * 
59
 * ...
60
 * 
61
 * boolean .compareAndSwapInt(Object obj, long fieldOffset, int expect, int update ) {
62
 *     if( obj.field == expect ) {
63
 *         obj.field = update;
64
 *         return true;
65
 *     }
66
 *     return false;
67
 * }
68
 * </code>
69
 * </pre>
70
 * 
71
 * @author Volker Berlin
72
 */
73
class UnsafeManager {
74

75
    /** Unsafe class bane in Java 8 */
76
    static final String                              UNSAFE_8  = "sun/misc/Unsafe";
77

78
    /** Unsafe class bane in Java 11 */
79
    static final String                              UNSAFE_11 = "jdk/internal/misc/Unsafe";
80

81
    /** Wrapper for Unsafe */
82
    static final String                              FIELDUPDATER = "java/util/concurrent/atomic/AtomicReferenceFieldUpdater";
83

84
    /** VARHANDLE as modern replacement of Unsafe */
85
    static final String                              VARHANDLE = "java/lang/invoke/VarHandle";
86

87
    static final String                              METHOD_HANDLES = "java/lang/invoke/MethodHandles$Lookup";
88

89
    @Nonnull
90
    private final FunctionManager                    functions;
91

92
    @Nonnull
93
    private final TypeManager                        types;
94

95
    @Nonnull
96
    private final ClassFileLoader                    classFileLoader;
97

98
    @Nonnull
1✔
99
    private final HashMap<FunctionName, UnsafeState> unsafes      = new HashMap<>();
100

101
    @Nonnull
1✔
102
    private final HashMap<Integer, UnsafeState>      localStates  = new HashMap<>();
103

104
    /**
105
     * Create an instance of the manager
106
     * 
107
     * @param options
108
     *            compiler option/properties
109
     * @param classFileLoader
110
     *            for loading the class files
111
     */
112
    UnsafeManager( @Nonnull WasmOptions options, @Nonnull ClassFileLoader classFileLoader ) {
1✔
113
        this.functions = options.functions;
1✔
114
        this.types = options.types;
1✔
115
        this.classFileLoader = classFileLoader;
1✔
116
    }
1✔
117

118
    /**
119
     * Replace any Unsafe API call with direct field access.
120
     * 
121
     * @param instructions
122
     *            the instruction list of a function/method
123
     * @throws IOException
124
     *             If any I/O error occur
125
     */
126
    void replaceUnsafe( @Nonnull List<WasmInstruction> instructions ) throws IOException {
127
        // search for Unsafe function calls
128
        localStates.clear();
1✔
129
        for( int i = 0; i < instructions.size(); i++ ) {
1✔
130
            WasmInstruction instr = instructions.get( i );
1✔
131
            switch( instr.getType() ) {
1✔
132
                case CallVirtual:
133
                case Call:
134
                    WasmCallInstruction callInst = (WasmCallInstruction)instr;
1✔
135
                    switch( callInst.getFunctionName().className ) {
1✔
136
                        case UNSAFE_8:
137
                        case UNSAFE_11:
138
                        case FIELDUPDATER:
139
                            patch( instructions, i, callInst );
1✔
140
                            break;
1✔
141
                        case VARHANDLE:
142
                        case METHOD_HANDLES:
143
                            patchVarHandle( instructions, i, callInst );
×
144
                            break;
145
                    }
146
                    break;
1✔
147
                default:
148
            }
149
        }
150
    }
1✔
151

152
    /**
153
     * Patch in the instruction list an Unsafe method call. It does not change the count of instructions.
154
     * 
155
     * @param instructions
156
     *            the instruction list of a function/method
157
     * @param idx
158
     *            the index in the instructions
159
     * @param callInst
160
     *            the method call to Unsafe
161
     * @throws IOException
162
     *             If any I/O error occur
163
     */
164
    private void patch( @Nonnull List<WasmInstruction> instructions, int idx, @Nonnull WasmCallInstruction callInst ) throws IOException {
165
        FunctionName name = callInst.getFunctionName();
1✔
166
        switch( name.signatureName ) {
1✔
167
            case "sun/misc/Unsafe.getUnsafe()Lsun/misc/Unsafe;":
168
            case "jdk/internal/misc/Unsafe.getUnsafe()Ljdk/internal/misc/Unsafe;":
169
                patch_getUnsafe( instructions, idx );
1✔
170
                break;
1✔
171
            case "sun/misc/Unsafe.objectFieldOffset(Ljava/lang/reflect/Field;)J":
172
            case "jdk/internal/misc/Unsafe.objectFieldOffset(Ljava/lang/reflect/Field;)J":
173
                patch_objectFieldOffset_Java8( instructions, idx, callInst );
1✔
174
                break;
1✔
175
            case "jdk/internal/misc/Unsafe.objectFieldOffset(Ljava/lang/Class;Ljava/lang/String;)J":
176
                patch_objectFieldOffset_Java11( instructions, idx, callInst, false );
×
177
                break;
×
178
            case "java/util/concurrent/atomic/AtomicReferenceFieldUpdater.newUpdater(Ljava/lang/Class;Ljava/lang/Class;Ljava/lang/String;)Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;":
179
                patch_objectFieldOffset_Java11( instructions, idx, callInst, true );
1✔
180
                break;
1✔
181
            case "sun/misc/Unsafe.arrayBaseOffset(Ljava/lang/Class;)I":
182
            case "jdk/internal/misc/Unsafe.arrayBaseOffset(Ljava/lang/Class;)I":
183
                patch_arrayBaseOffset( instructions, idx, callInst );
×
184
                break;
×
185
            case "sun/misc/Unsafe.arrayIndexScale(Ljava/lang/Class;)I":
186
            case "jdk/internal/misc/Unsafe.arrayIndexScale(Ljava/lang/Class;)I":
187
                patch_arrayIndexScale( instructions, idx, callInst );
×
188
                break;
×
189
            case "sun/misc/Unsafe.getObjectVolatile(Ljava/lang/Object;J)Ljava/lang/Object;":
190
            case "sun/misc/Unsafe.getInt(Ljava/lang/Object;J)I":
191
            case "sun/misc/Unsafe.getLong(Ljava/lang/Object;J)J":
192
            case "jdk/internal/misc/Unsafe.getInt(Ljava/lang/Object;J)I":
193
            case "jdk/internal/misc/Unsafe.getLong(Ljava/lang/Object;J)J":
194
            case "jdk/internal/misc/Unsafe.getObject(Ljava/lang/Object;J)Ljava/lang/Object;":
195
                patchFieldFunction( instructions, idx, callInst, name, 1 );
1✔
196
                break;
1✔
197
            case "sun/misc/Unsafe.getAndAddInt(Ljava/lang/Object;JI)I":
198
            case "sun/misc/Unsafe.getAndSetInt(Ljava/lang/Object;JI)I":
199
            case "sun/misc/Unsafe.putOrderedInt(Ljava/lang/Object;JI)V":
200
            case "sun/misc/Unsafe.putInt(Ljava/lang/Object;JI)V":
201
            case "sun/misc/Unsafe.getAndAddLong(Ljava/lang/Object;JJ)J":
202
            case "sun/misc/Unsafe.getAndSetLong(Ljava/lang/Object;JJ)J":
203
            case "sun/misc/Unsafe.putOrderedLong(Ljava/lang/Object;JJ)V":
204
            case "sun/misc/Unsafe.putLong(Ljava/lang/Object;JJ)V":
205
            case "sun/misc/Unsafe.putOrderedObject(Ljava/lang/Object;JLjava/lang/Object;)V":
206
            case "sun/misc/Unsafe.putObjectVolatile(Ljava/lang/Object;JLjava/lang/Object;)V":
207
            case "sun/misc/Unsafe.putObject(Ljava/lang/Object;JLjava/lang/Object;)V":
208
            case "sun/misc/Unsafe.getAndSetObject(Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object;":
209
            case "jdk/internal/misc/Unsafe.getAndAddInt(Ljava/lang/Object;JI)I":
210
            case "jdk/internal/misc/Unsafe.getAndSetInt(Ljava/lang/Object;JI)I":
211
            case "jdk/internal/misc/Unsafe.putIntRelease(Ljava/lang/Object;JI)V":
212
            case "jdk/internal/misc/Unsafe.putInt(Ljava/lang/Object;JI)V":
213
            case "jdk/internal/misc/Unsafe.getAndAddLong(Ljava/lang/Object;JJ)J":
214
            case "jdk/internal/misc/Unsafe.getAndSetLong(Ljava/lang/Object;JJ)J":
215
            case "jdk/internal/misc/Unsafe.putLongRelease(Ljava/lang/Object;JJ)V":
216
            case "jdk/internal/misc/Unsafe.putLongVolatile(Ljava/lang/Object;JJ)V":
217
            case "jdk/internal/misc/Unsafe.putLong(Ljava/lang/Object;JJ)V":
218
            case "jdk/internal/misc/Unsafe.putObject(Ljava/lang/Object;JLjava/lang/Object;)V":
219
            case "jdk/internal/misc/Unsafe.getObjectAcquire(Ljava/lang/Object;J)Ljava/lang/Object;":
220
            case "jdk/internal/misc/Unsafe.putObjectRelease(Ljava/lang/Object;JLjava/lang/Object;)V":
221
                patchFieldFunction( instructions, idx, callInst, name, 2 );
1✔
222
                break;
1✔
223
            case "sun/misc/Unsafe.compareAndSwapInt(Ljava/lang/Object;JII)Z":
224
            case "sun/misc/Unsafe.compareAndSwapLong(Ljava/lang/Object;JJJ)Z":
225
            case "sun/misc/Unsafe.compareAndSwapObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z":
226
            case "jdk/internal/misc/Unsafe.compareAndSetInt(Ljava/lang/Object;JII)Z":
227
            case "jdk/internal/misc/Unsafe.compareAndSetLong(Ljava/lang/Object;JJJ)Z":
228
            case "jdk/internal/misc/Unsafe.compareAndSetObject(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z":
229
                patchFieldFunction( instructions, idx, callInst, name, 3 );
1✔
230
                break;
1✔
231
            case "java/util/concurrent/atomic/AtomicReferenceFieldUpdater.compareAndSet(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z":
232
                patchFieldFunction( instructions, idx, callInst, name, 4 );
1✔
233
                break;
1✔
234
            case "jdk/internal/misc/Unsafe.getCharUnaligned(Ljava/lang/Object;JZ)C":
235
            case "jdk/internal/misc/Unsafe.getShortUnaligned(Ljava/lang/Object;JZ)S":
236
            case "jdk/internal/misc/Unsafe.getIntUnaligned(Ljava/lang/Object;J)I":
237
            case "jdk/internal/misc/Unsafe.getIntUnaligned(Ljava/lang/Object;JZ)I":
238
            case "jdk/internal/misc/Unsafe.getLongUnaligned(Ljava/lang/Object;J)J":
239
            case "jdk/internal/misc/Unsafe.getLongUnaligned(Ljava/lang/Object;JZ)J":
240
                patch_getLongUnaligned( instructions, idx, callInst, name );
×
241
                break;
×
242
            case "jdk/internal/misc/Unsafe.isBigEndian()Z":
243
                patch_isBigEndian( instructions, idx, callInst );
×
244
                break;
×
245
            case "jdk/internal/misc/Unsafe.shouldBeInitialized(Ljava/lang/Class;)Z":
246
                replaceWithConstNumber( instructions, idx, callInst, 2, 0 );
×
247
                break;
×
248
            case "jdk/internal/misc/Unsafe.storeFence()V":
249
                remove( instructions, idx, callInst, 1 );
×
250
                break;
×
251
            case "jdk/internal/misc/Unsafe.ensureClassInitialized(Ljava/lang/Class;)V":
252
            case "jdk/internal/misc/Unsafe.unpark(Ljava/lang/Object;)V":
253
            case "sun/misc/Unsafe.unpark(Ljava/lang/Object;)V":
254
                remove( instructions, idx, callInst, 2 );
×
255
                break;
×
256
            case "sun/misc/Unsafe.park(ZJ)V":
257
            case "jdk/internal/misc/Unsafe.park(ZJ)V":
258
                remove( instructions, idx, callInst, 3 );
×
259
                break;
×
260
            default:
261
                throw new WasmException( "Unsupported Unsafe method: " + name.signatureName, -1 );
×
262
        }
263
    }
1✔
264

265
    /**
266
     * Replace a call to Unsafe.getUnsafe() with a NOP operation.
267
     * 
268
     * @param instructions
269
     *            the instruction list of a function/method
270
     * @param idx
271
     *            the index in the instructions
272
     */
273
    private void patch_getUnsafe( @Nonnull List<WasmInstruction> instructions, int idx ) {
274
        WasmInstruction instr = instructions.get( idx + 1 );
1✔
275

276
        int to = idx + (instr.getType() == Type.Global ? 2 : 1);
1✔
277

278
        nop( instructions, idx, to );
1✔
279
    }
1✔
280

281
    /**
282
     * Find the field on which the offset is assign: long FIELD_OFFSET = UNSAFE.objectFieldOffset(...
283
     * 
284
     * @param instructions
285
     *            the instruction list of a function/method
286
     * @param idx
287
     *            the index in the instructions
288
     * @return the state
289
     */
290
    @Nonnull
291
    private UnsafeState findUnsafeState( @Nonnull List<WasmInstruction> instructions, int idx ) {
292
        // find the field on which the offset is assign: long FIELD_OFFSET = UNSAFE.objectFieldOffset(...
293
        WasmInstruction instr;
294
        idx++;
1✔
295
        INSTR: do {
296
            instr = instructions.get( idx );
1✔
297
            switch( instr.getType() ) {
1✔
298
                case Convert:
299
                    idx++;
×
300
                    continue INSTR;
×
301
                case Global:
302
                    break;
1✔
303
                case Jump:
304
                    int pos = ((JumpInstruction)instr).getJumpPosition();
×
305
                    for( idx++; idx < instructions.size(); idx++) {
×
306
                        instr = instructions.get( idx );
×
307
                        if( instr.getCodePosition() >= pos ) {
×
308
                            break;
×
309
                        }
310
                    }
311
                    continue INSTR;
312
                case Local:
313
                    // occur with jdk.internal.misc.InnocuousThread
314
                    UnsafeState state = new UnsafeState();
×
315
                    localStates.put( ((WasmLocalInstruction)instr).getIndex(), state );
×
316
                    return state;
×
317
                default:
318
                    throw new WasmException( "Unsupported assign operation for Unsafe field offset: " + instr.getType(), -1 );
×
319
            }
320
            break;
321
        } while( true );
322
        FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName();
1✔
323
        UnsafeState state = unsafes.get( fieldNameWithOffset );
1✔
324
        if( state == null ) {
1✔
325
            unsafes.put( fieldNameWithOffset, state = new UnsafeState() );
1✔
326
        }
327
        return state;
1✔
328
    }
329

330
    /**
331
     * Get the class name from the stack value. It is searching a WasmConstClassInstruction that produce the value of
332
     * the stack value.
333
     * 
334
     * @param instructions
335
     *            the instruction list of a function/method
336
     * @param stackValue
337
     *            the stack value (instruction and position that produce an stack value)
338
     * @return the class name like: java/lang/String
339
     */
340
    @Nonnull
341
    private static String getClassConst( List<WasmInstruction> instructions, StackValue stackValue ) {
342
        WasmInstruction instr = stackValue.instr;
1✔
343
        switch( instr.getType() ) {
1✔
344
            case Local:
345
                int slot = ((WasmLocalInstruction)instr).getSlot();
1✔
346
                for( int i = stackValue.idx - 1; i >= 0; i-- ) {
1✔
347
                    instr = instructions.get( i );
1✔
348
                    if( instr.getType() == Type.Local ) {
1✔
349
                        WasmLocalInstruction loadInstr = (WasmLocalInstruction)instr;
1✔
350
                        if( loadInstr.getSlot() == slot && loadInstr.getOperator() == VariableOperator.set ) {
1✔
351
                            stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, i ), 1, instr.getCodePosition() );
1✔
352
                            instr = stackValue.instr;
1✔
353
                            break;
1✔
354
                        }
355

356
                    }
357
                }
358
                break;
1✔
359
            default:
360
        }
361
        return ((WasmConstClassInstruction)instr).getValue();
1✔
362
    }
363

364
    /**
365
     * Patch a method call to Unsafe.objectFieldOffset() and find the parameter for other patch operations.
366
     * 
367
     * @param instructions
368
     *            the instruction list
369
     * @param idx
370
     *            the index in the instructions
371
     * @param callInst
372
     *            the method call to Unsafe
373
     * @throws IOException
374
     *             If any I/O error occur
375
     */
376
    private void patch_objectFieldOffset_Java8( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) throws IOException {
377
        UnsafeState state = findUnsafeState( instructions, idx );
1✔
378

379
        // objectFieldOffset() has 2 parameters THIS(Unsafe) and a Field
380
        List<WasmInstruction> paramInstructions = instructions.subList( 0, idx );
1✔
381
        int from = StackInspector.findInstructionThatPushValue( paramInstructions, 2, callInst.getCodePosition() ).idx;
1✔
382

383
        StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 1, callInst.getCodePosition() );
1✔
384
        WasmInstruction instr = stackValue.instr;
1✔
385
        WasmCallInstruction fieldInst = (WasmCallInstruction)instr;
1✔
386

387
        FunctionName fieldFuncName = fieldInst.getFunctionName();
1✔
388
        switch( fieldFuncName.signatureName ) {
1✔
389
            case "java/lang/Class.getDeclaredField(Ljava/lang/String;)Ljava/lang/reflect/Field;":
390
                stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, stackValue.idx ), 1, fieldInst.getCodePosition() );
1✔
391
                state.fieldName = ((WasmConstStringInstruction)stackValue.instr).getValue();
1✔
392

393
                // find the class value on which getDeclaredField is called
394
                stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, stackValue.idx ), 1, fieldInst.getCodePosition() );
1✔
395
                state.typeName = getClassConst( instructions, stackValue );
1✔
396
                break;
1✔
397

398
            default:
399
                throw new WasmException( "Unsupported Unsafe method to get target field: " + fieldFuncName.signatureName, -1 );
×
400
        }
401

402
        useFieldName( state );
1✔
403
        nop( instructions, from, idx + 2 );
1✔
404
    }
1✔
405

406
    /**
407
     * Patch a method call to Unsafe.objectFieldOffset() and find the parameter for other patch operations.
408
     * 
409
     * @param instructions
410
     *            the instruction list
411
     * @param idx
412
     *            the index in the instructions
413
     * @param callInst
414
     *            the method call to Unsafe
415
     * @param isAtomicReferenceFieldUpdater
416
     *            true, if is AtomicReferenceFieldUpdater
417
     * @throws IOException
418
     *             If any I/O error occur
419
     */
420
    private void patch_objectFieldOffset_Java11( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst, boolean isAtomicReferenceFieldUpdater ) throws IOException {
421
        UnsafeState state = findUnsafeState( instructions, idx );
1✔
422

423
        // objectFieldOffset() has 3 parameters THIS(Unsafe), class and the fieldname
424
        List<WasmInstruction> paramInstructions = instructions.subList( 0, idx );
1✔
425
        int from = StackInspector.findInstructionThatPushValue( paramInstructions, 3, callInst.getCodePosition() ).idx;
1✔
426

427
        StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 1, callInst.getCodePosition() );
1✔
428
        state.fieldName = ((WasmConstStringInstruction)stackValue.instr).getValue();
1✔
429

430
        // find the class value on which getDeclaredField is called
431
        int classParamIdx = isAtomicReferenceFieldUpdater ? 3 : 2;
1✔
432
        stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, classParamIdx, callInst.getCodePosition() );
1✔
433
        state.typeName = getClassConst( instructions, stackValue );
1✔
434

435
        useFieldName( state );
1✔
436
        nop( instructions, from, idx + 2 );
1✔
437
    }
1✔
438

439
    /**
440
     * Patch a method call to Unsafe.arrayBaseOffset() and find the parameter for other patch operations.
441
     * 
442
     * @param instructions
443
     *            the instruction list
444
     * @param idx
445
     *            the index in the instructions
446
     * @param callInst
447
     *            the method call to Unsafe
448
     */
449
    private void patch_arrayBaseOffset( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) {
450
        UnsafeState state = findUnsafeState( instructions, idx );
×
451

452
        // objectFieldOffset() has 2 parameters THIS(Unsafe) and a Class from an array
453
        List<WasmInstruction> paramInstructions = instructions.subList( 0, idx );
×
454
        int from = StackInspector.findInstructionThatPushValue( paramInstructions, 2, callInst.getCodePosition() ).idx;
×
455

456
        StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 1, callInst.getCodePosition() );
×
457
        state.typeName = getClassConst( instructions, stackValue );
×
458

459
        nop( instructions, from, idx );
×
460
        // we put the constant value 0 on the stack, we does not need array base offset in WASM
461
        instructions.set( idx, new WasmConstNumberInstruction( 0, callInst.getCodePosition(), callInst.getLineNumber() ) );
×
462
    }
×
463

464
    /**
465
     * Patch method call to Unsafe.arrayIndexScale()
466
     * 
467
     * @param instructions
468
     *            the instruction list
469
     * @param idx
470
     *            the index in the instructions
471
     * @param callInst
472
     *            the method call to Unsafe
473
     */
474
    private void patch_arrayIndexScale( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) {
475
        int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 2, callInst.getCodePosition() ).idx;
×
476

477
        nop( instructions, from, idx );
×
478
        // we put the constant value 1 on the stack because we does not want shift array positions
479
        instructions.set( idx, new WasmConstNumberInstruction( 1, callInst.getCodePosition(), callInst.getLineNumber() ) );
×
480
    }
×
481

482
    /**
483
     * Mark the field as used
484
     * 
485
     * @param state
486
     *            the state
487
     * @throws IOException
488
     *             If any I/O error occur
489
     */
490
    private void useFieldName( @Nonnull UnsafeState state ) throws IOException {
491
        FieldInfo fieldInfo = classFileLoader.get( state.typeName ).getField( state.fieldName );
1✔
492
        NamedStorageType fieldName = new NamedStorageType( state.typeName, fieldInfo, types );
1✔
493
        types.valueOf( state.typeName ).useFieldName( fieldName );
1✔
494
    }
1✔
495

496
    /**
497
     * Patch an unsafe function that access a field
498
     * 
499
     * @param instructions
500
     *            the instruction list
501
     * @param idx
502
     *            the index in the instructions
503
     * @param callInst
504
     *            the method call to Unsafe
505
     * @param name
506
     *            the calling function
507
     * @param fieldNameParam
508
     *            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.
509
     */
510
    private void patchFieldFunction( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) {
511
        StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() );
1✔
512
        WasmInstruction instr = stackValue.instr;
1✔
513

514
        Set<FunctionName> fieldNames;
515
        FunctionName fieldNameWithOffset = null;
1✔
516
        UnsafeState state = null;
1✔
517
        if( instr.getType() == Type.Global ) {
1✔
518
            fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName();
1✔
519
            fieldNames = Collections.singleton( fieldNameWithOffset );
1✔
520
        } else {
521
            fieldNames = new HashSet<>();
×
522
            if( instr.getType() == Type.Local ) {
×
523
                // occur with jdk.internal.misc.InnocuousThread
524
                state = localStates.get(  ((WasmLocalInstruction)instr).getIndex() );
×
525
                if( state != null ) {
×
526
                    fieldNameWithOffset = new FunctionName( state.typeName, state.fieldName, "" );
×
527
                }
528
            }
529
            if( fieldNameWithOffset == null ) {
×
530
                // java.util.concurrent.ConcurrentHashMap.tabAt() calculate a value with the field
531
                int pos2 = stackValue.idx;
×
532
                stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam + 1, callInst.getCodePosition() );
×
533
                int i = stackValue.idx;
×
534
                for( ; i < pos2; i++ ) {
×
535
                    instr = instructions.get( i );
×
536
                    if( instr.getType() != Type.Global ) {
×
537
                        continue;
×
538
                    }
539
                    fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName();
×
540
                    fieldNames.add( fieldNameWithOffset );
×
541
                }
542
            }
543
        }
544

545
        UnsafeState state_ = state;
1✔
546

547
        WatCodeSyntheticFunctionName func =
1✔
548
                        new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + fieldNameWithOffset.methodName + '.' + name.methodName, name.signature, "", (AnyType[])null ) {
1✔
549
                            @Override
550
                            protected String getCode() {
551
                                UnsafeState state = state_;
1✔
552
                                for(FunctionName fieldNameWithOffset : fieldNames ) {
1✔
553
                                    if( state != null ) {
1✔
554
                                        break;
×
555
                                    }
556
                                    state = unsafes.get( fieldNameWithOffset );
1✔
557
                                }
1✔
558
                                if( state == null ) {
1✔
559
                                    if( functions.isFinish() ) {
1✔
560
                                        throw new RuntimeException( this.fullName + name.signature );
×
561
                                    }
562
                                    // we are in the scan phase. The static code was not scanned yet.
563
                                    return "";
1✔
564
                                }
565
                                AnyType[] paramTypes = callInst.getPopValueTypes();
1✔
566
                                switch( name.methodName ) {
1✔
567
                                    case "compareAndSwapInt":
568
                                    case "compareAndSetInt":
569
                                    case "compareAndSwapLong":
570
                                    case "compareAndSetLong":
571
                                    case "compareAndSwapObject":
572
                                    case "compareAndSetObject":
573
                                    case "compareAndSet": // AtomicReferenceFieldUpdater
574
                                        AnyType type = paramTypes[3];
1✔
575
                                        if( type.isRefType() ) {
1✔
576
                                            type = ValueType.ref;
1✔
577
                                        }
578
                                        if( state.fieldName != null ) {
1✔
579
                                            // field access
580
                                            int paramOffset = "java/util/concurrent/atomic/AtomicReferenceFieldUpdater".equals( name.className ) ? -1 : 0;
1✔
581
                                            return "local.get 1" // THIS
1✔
582
                                                            + " struct.get " + state.typeName + ' ' + state.fieldName //
583
                                                            + " local.get " + (3 + paramOffset) // expected
584
                                                            + " " + type + ".eq" //
585
                                                            + " if" //
586
                                                            + "   local.get 1" // THIS
587
                                                            + "   local.get " + (4 + paramOffset) // update
588
                                                            + "   struct.set " + state.typeName + ' ' + state.fieldName //
589
                                                            + "   i32.const 1" //
590
                                                            + "   return" //
591
                                                            + " end" //
592
                                                            + " i32.const 1" //
593
                                                            + " return";
594
                                        } else {
595
                                            // array access
596
                                            return "local.get 1" // THIS
×
597
                                                            + " local.get 2" // the array index
598
                                                            + " i32.wrap_i64" // long -> int
599
                                                            + " array.get " + state.typeName //
600
                                                            + " local.get 3 " // expected
601
                                                            + " " + type + ".eq" //
602
                                                            + " if" //
603
                                                            + "   local.get 1" // THIS
604
                                                            + "   local.get 2" // the array index
605
                                                            + "   i32.wrap_i64" // long -> int
606
                                                            + "   local.get 4 " // update
607
                                                            + "   array.set " + state.typeName //
608
                                                            + "   i32.const 1" //
609
                                                            + "   return" //
610
                                                            + " end" //
611
                                                            + " i32.const 1" //
612
                                                            + " return";
613
                                        }
614

615
                                    case "getAndAddInt":
616
                                    case "getAndAddLong":
617
                                        return "local.get 1" // THIS
1✔
618
                                                        + " local.get 1" // THIS
619
                                                        + " struct.get " + state.typeName + ' ' + state.fieldName //
620
                                                        + " local.tee 4" // temp
621
                                                        + " local.get 3 " // delta
622
                                                        + paramTypes[3] + ".add" //
623
                                                        + " struct.set " + state.typeName + ' ' + state.fieldName //
624
                                                        + " local.get 4" // temp
625
                                                        + " return";
626

627
                                    case "getAndSetInt":
628
                                    case "getAndSetLong":
629
                                    case "getAndSetObject":
630
                                        return "local.get 1" // THIS
1✔
631
                                                        + " struct.get " + state.typeName + ' ' + state.fieldName //
632
                                                        + " local.get 1" // THIS
633
                                                        + " local.get 3" // newValue
634
                                                        + " struct.set " + state.typeName + ' ' + state.fieldName //
635
                                                        + " return";
636

637
                                    case "putOrderedInt":
638
                                    case "putInt":
639
                                    case "putOrderedLong":
640
                                    case "putLong":
641
                                    case "putLongVolatile":
642
                                    case "putOrderedObject":
643
                                    case "putObjectVolatile":
644
                                    case "putObject":
645
                                    case "putObjectRelease":
646
                                        if( state.fieldName != null ) {
1✔
647
                                            // field access
648
                                            return "local.get 1" // THIS
1✔
649
                                                            + " local.get 3" // x
650
                                                            + " struct.set " + state.typeName + ' ' + state.fieldName;
651
                                        } else {
652
                                            // array access
653
                                            return "local.get 1" // THIS
×
654
                                                            + " local.get 2" // the array index
655
                                                            + " i32.wrap_i64" // long -> int
656
                                                            + " local.get 3" // x
657
                                                            + " array.set " + state.typeName;
658
                                        }
659

660
                                    case "getInt":
661
                                    case "getLong":
662
                                    case "getObject":
663
                                    case "getObjectVolatile":
664
                                        if( state.fieldName != null ) {
1✔
665
                                            // field access
666
                                            return "local.get 1" // THIS
1✔
667
                                                            + " struct.get " + state.typeName + ' ' + state.fieldName //
668
                                                            + " return";
669
                                        } else {
670
                                            // array access
671
                                            return "local.get 1" // array
×
672
                                                            + " local.get 2" // the array index
673
                                                            + " i32.wrap_i64" // long -> int
674
                                                            + " array.get " + state.typeName + " return";
675
                                        }
676
                                }
677

678
                                throw new RuntimeException( name.signatureName );
×
679
                            }
680
                        };
681
        boolean needThisParameter = true;
1✔
682
        functions.markAsNeeded( func, needThisParameter ); // original function has an THIS parameter of the Unsafe instance, we need to consume it
1✔
683
        WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), needThisParameter );
1✔
684
        instructions.set( idx, call );
1✔
685

686
        // a virtual method call has also a DUP of this because we need for virtual method dispatch the parameter 2 times.
687
        for( int i = idx; i >= 0; i-- ) {
1✔
688
            instr = instructions.get( i );
1✔
689
            if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) {
1✔
690
                nop( instructions, i, i + 1 );
1✔
691
                break;
1✔
692
            }
693
        }
694
    }
1✔
695

696
    /**
697
     * Patch an unsafe function that access a field
698
     * 
699
     * @param instructions
700
     *            the instruction list
701
     * @param idx
702
     *            the index in the instructions
703
     * @param callInst
704
     *            the method call to Unsafe
705
     */
706
    private void patch_getLongUnaligned( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, FunctionName name ) {
707
        WatCodeSyntheticFunctionName func = new WatCodeSyntheticFunctionName( "", name.methodName, name.signature, "unreachable", (AnyType[])null );
×
708
        functions.markAsNeeded( func, false );
×
709
        WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), false );
×
710
        instructions.set( idx, call );
×
711
    }
×
712

713
    /**
714
     * Patch an unsafe function that access a field
715
     * 
716
     * @param instructions
717
     *            the instruction list
718
     * @param idx
719
     *            the index in the instructions
720
     * @param callInst
721
     *            the method call to Unsafe
722
     */
723
    private void patch_isBigEndian( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst ) {
724
//        int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 1, callInst.getCodePosition() ).idx;
725
//
726
//        nop( instructions, from, idx );
727

728
        // on x86 use little endian
729
        instructions.set( idx, new WasmConstNumberInstruction( 0, callInst.getCodePosition(), callInst.getLineNumber() ) );
×
730
    }
×
731

732
    /**
733
     * Patch an unsafe function that access a field
734
     * 
735
     * @param instructions
736
     *            the instruction list
737
     * @param idx
738
     *            the index in the instructions
739
     * @param callInst
740
     *            the method call to Unsafe
741
     */
742
    private void replaceWithConstNumber( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, int paramCount, int number ) {
743
        int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 1, callInst.getCodePosition() ).idx;
×
744

745
        nop( instructions, from, idx );
×
746

747
        // on x86 use little endian
748
        instructions.set( idx, new WasmConstNumberInstruction( number, callInst.getCodePosition(), callInst.getLineNumber() ) );
×
749
    }
×
750

751
    /**
752
     * Remove an unsafe function that access a field
753
     * 
754
     * @param instructions
755
     *            the instruction list
756
     * @param idx
757
     *            the index in the instructions
758
     * @param callInst
759
     *            the method call to Unsafe
760
     * @param paramCount
761
     *            the count of params that must be removed from stack (including THIS if instance method)
762
     */
763
    private void remove( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, int paramCount ) {
764
        int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), paramCount, callInst.getCodePosition() ).idx;
×
765

766
        nop( instructions, from, idx + 1 );
×
767
    }
×
768

769
    /**
770
     * Replace the instructions with NOP operations
771
     * 
772
     * @param instructions
773
     *            the instruction list
774
     * @param from
775
     *            starting index
776
     * @param to
777
     *            end index
778
     */
779
    private void nop( List<WasmInstruction> instructions, int from, int to ) {
780
        for( int i = from; i < to; i++ ) {
1✔
781
            WasmInstruction instr = instructions.get( i );
1✔
782
            instructions.set( i, new WasmNopInstruction( instr.getCodePosition(), instr.getLineNumber() ) );
1✔
783
        }
784
    }
1✔
785

786
    private void patchVarHandle( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) throws IOException {
787
        FunctionName name = callInst.getFunctionName();
×
788
        switch( name.methodName ) {
×
789
            case "findVarHandle":
790
                patch_findVarHandle( instructions, idx, callInst );
×
791
                break;
×
792
            case "arrayElementVarHandle": // java/lang/invoke/MethodHandles
793
                UnsafeState state = findUnsafeState( instructions, idx );
×
794
                int from = StackInspector.findInstructionThatPushValue( instructions, 2, callInst.getCodePosition() ).idx;
×
795

796
                StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions, 1, callInst.getCodePosition() );
×
797
                state.typeName = getClassConst( instructions, stackValue );
×
798
                break;
×
799
            case "getAndSet":
800
            case "set":
801
            case "getAcquire":
802
            case "getAndAdd":
803
            case "getAndBitwiseOr":
804
            case "get":
805
            case "compareAndSet":
806
            case "weakCompareAndSet":
807
            case "setVolatile":
808
            case "setRelease":
809
            case "setOpaque":
810
                patchVarHandleFieldFunction( instructions, idx, callInst, name, callInst.getPopCount() );
×
811
                break;
×
812
            case "releaseFence":
813
                nop( instructions, idx, idx + 1 );
×
814
                break;
×
815
            default:
816
                throw new WasmException( "Unsupported VarHandle method: " + name.signatureName, -1 );
×
817
        }
818
    }
×
819

820
    /**
821
     * Patch a method call to Unsafe.objectFieldOffset() and find the parameter for other patch operations.
822
     * 
823
     * @param instructions
824
     *            the instruction list
825
     * @param idx
826
     *            the index in the instructions
827
     * @param callInst
828
     *            the method call to Unsafe
829
     * @throws IOException
830
     *             If any I/O error occur
831
     */
832
    private void patch_findVarHandle( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) throws IOException {
833
        UnsafeState state = findUnsafeState( instructions, idx );
×
834

835
        // objectFieldOffset() has 3 parameters THIS(Unsafe), class and the fieldname
836
        List<WasmInstruction> paramInstructions = instructions.subList( 0, idx );
×
837
        int from = StackInspector.findInstructionThatPushValue( paramInstructions, 4, callInst.getCodePosition() ).idx;
×
838

839
        StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 2, callInst.getCodePosition() );
×
840
        state.fieldName = ((WasmConstStringInstruction)stackValue.instr).getValue();
×
841

842
        // find the class value on which getDeclaredField is called
843
        stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 3, callInst.getCodePosition() );
×
844
        state.typeName = getClassConst( instructions, stackValue );
×
845

846
        useFieldName( state );
×
847
        nop( instructions, from, idx + 2 );
×
848
    }
×
849

850
    /**
851
     * Patch an unsafe function that access a field
852
     * 
853
     * @param instructions
854
     *            the instruction list
855
     * @param idx
856
     *            the index in the instructions
857
     * @param callInst
858
     *            the method call to Unsafe
859
     * @param name
860
     *            the calling function
861
     * @param fieldNameParam
862
     *            the function parameter with the field offset on the stack. This must be a long (Java signature "J") for Unsafe.
863
     */
864
    private void patchVarHandleFieldFunction( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) {
865
        StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() );
×
866
        FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)stackValue.instr).getFieldName();
×
867
        WatCodeSyntheticFunctionName func =
×
868
                        new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + name.methodName, name.signature, "", (AnyType[])null ) {
×
869
                            @Override
870
                            protected String getCode() {
871
                                UnsafeState state = unsafes.get( fieldNameWithOffset );
×
872
                                if( state == null ) {
×
873
                                    // we are in the scan phase. The static code was not scanned yet.
874
                                    return "";
×
875
                                }
876
                                AnyType[] paramTypes = callInst.getPopValueTypes();
×
877
                                switch( name.methodName ) {
×
878
                                    case "compareAndSet":
879
                                    case "weakCompareAndSet":
880
                                        AnyType type = paramTypes[3];
×
881
                                        if( type.isRefType() ) {
×
882
                                            type = ValueType.ref;
×
883
                                        }
884
                                        return "local.get 1" // THIS
×
885
                                                        + " struct.get " + state.typeName + ' ' + state.fieldName //
886
                                                        + " local.get 2 " // expected
887
                                                        + " " + type + ".eq" //
888
                                                        + " if" //
889
                                                        + "   local.get 1" // THIS
890
                                                        + "   local.get 3 " // update
891
                                                        + "   struct.set " + state.typeName + ' ' + state.fieldName //
892
                                                        + "   i32.const 1" //
893
                                                        + "   return" //
894
                                                        + " end" //
895
                                                        + " i32.const 1" //
896
                                                        + " return";
897

898
                                    case "getAndSet":
899
                                        return "local.get 1" // THIS
×
900
                                                        + " struct.get " + state.typeName + ' ' + state.fieldName //
901
                                                        + " local.get 1" // THIS
902
                                                        + " local.get 2" // newValue
903
                                                        + " struct.set " + state.typeName + ' ' + state.fieldName //
904
                                                        + " return";
905

906
                                    case "set":
907
                                        return "local.get 1" // THIS
×
908
                                                        + " local.get 2" // x
909
                                                        + " struct.set " + state.typeName + ' ' + state.fieldName;
910
                                }
911

912
                                throw new RuntimeException( name.signatureName );
×
913
                            }
914
                        };
915
        functions.markAsNeeded( func, false );
×
916
        WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), false );
×
917
        instructions.set( idx, call );
×
918

919
        // a virtual method call has also a DUP of this because we need for virtual method dispatch the parameter 2 times.
920
        for( int i = idx; i >= 0; i-- ) {
×
921
            WasmInstruction instr = instructions.get( i );
×
922
            if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) {
×
923
                nop( instructions, i, i + 1 );
×
924
                break;
×
925
            }
926
        }
927
    }
×
928

929
    /**
930
     * Hold the state from declaring of Unsafe address
931
     */
932
    static class UnsafeState {
1✔
933
        String fieldName;
934

935
        String typeName;
936
    }
937
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc