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

i-net-software / JWebAssembly / 529

pending completion
529

push

travis-ci-com

Horcrux7
fix Unsafe for array elements

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

5925 of 6780 relevant lines covered (87.39%)

0.87 hits per line

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

58.77
/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
    @Nonnull
88
    private final FunctionManager                    functions;
89

90
    @Nonnull
91
    private final TypeManager                        types;
92

93
    @Nonnull
94
    private final ClassFileLoader                    classFileLoader;
95

96
    private final HashMap<FunctionName, UnsafeState> unsafes   = new HashMap<>();
1✔
97

98

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

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

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

250
    /**
251
     * Replace a call to Unsafe.getUnsafe() with a NOP operation.
252
     * 
253
     * @param instructions
254
     *            the instruction list of a function/method
255
     * @param idx
256
     *            the index in the instructions
257
     */
258
    private void patch_getUnsafe( List<WasmInstruction> instructions, int idx ) {
259
        WasmInstruction instr = instructions.get( idx + 1 );
1✔
260

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

263
        nop( instructions, idx, to );
1✔
264
    }
1✔
265

266
    /**
267
     * Find the field on which the offset is assign: long FIELD_OFFSET = UNSAFE.objectFieldOffset(...
268
     * 
269
     * @param instructions
270
     *            the instruction list of a function/method
271
     * @param idx
272
     *            the index in the instructions
273
     * @return the state
274
     */
275
    @Nonnull
276
    private UnsafeState findUnsafeState( List<WasmInstruction> instructions, int idx ) {
277
        // find the field on which the offset is assign: long FIELD_OFFSET = UNSAFE.objectFieldOffset(...
278
        WasmInstruction instr;
279
        idx++;
1✔
280
        INSTR: do {
281
            instr = instructions.get( idx );
1✔
282
            switch( instr.getType() ) {
1✔
283
                case Convert:
284
                    idx++;
×
285
                    continue INSTR;
×
286
                case Global:
287
                    break;
1✔
288
                case Jump:
289
                    int pos = ((JumpInstruction)instr).getJumpPosition();
×
290
                    for( idx++; idx < instructions.size(); idx++) {
×
291
                        instr = instructions.get( idx );
×
292
                        if( instr.getCodePosition() >= pos ) {
×
293
                            break;
×
294
                        }
295
                    }
296
                    continue INSTR;
297
                default:
298
                    throw new WasmException( "Unsupported assign operation for Unsafe field offset: " + instr.getType(), -1 );
×
299
            }
300
            break;
301
        } while( true );
302
        FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName();
1✔
303
        UnsafeState state = unsafes.get( fieldNameWithOffset );
1✔
304
        if( state == null ) {
1✔
305
            unsafes.put( fieldNameWithOffset, state = new UnsafeState() );
1✔
306
        }
307
        return state;
1✔
308
    }
309

310
    /**
311
     * Get the class name from the stack value. It is searching a WasmConstClassInstruction that produce the value of
312
     * the stack value.
313
     * 
314
     * @param instructions
315
     *            the instruction list of a function/method
316
     * @param stackValue
317
     *            the stack value (instruction and position that produce an stack value)
318
     * @return the class name like: java/lang/String
319
     */
320
    @Nonnull
321
    private static String getClassConst( List<WasmInstruction> instructions, StackValue stackValue ) {
322
        WasmInstruction instr = stackValue.instr;
1✔
323
        switch( instr.getType() ) {
1✔
324
            case Local:
325
                int slot = ((WasmLocalInstruction)instr).getSlot();
1✔
326
                for( int i = stackValue.idx - 1; i >= 0; i-- ) {
1✔
327
                    instr = instructions.get( i );
1✔
328
                    if( instr.getType() == Type.Local ) {
1✔
329
                        WasmLocalInstruction loadInstr = (WasmLocalInstruction)instr;
1✔
330
                        if( loadInstr.getSlot() == slot && loadInstr.getOperator() == VariableOperator.set ) {
1✔
331
                            stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, i ), 1, instr.getCodePosition() );
1✔
332
                            instr = stackValue.instr;
1✔
333
                            break;
1✔
334
                        }
335

336
                    }
337
                }
338
                break;
1✔
339
            default:
340
        }
341
        return ((WasmConstClassInstruction)instr).getValue();
1✔
342
    }
343

344
    /**
345
     * Patch a method call to Unsafe.objectFieldOffset() and find the parameter for other patch operations.
346
     * 
347
     * @param instructions
348
     *            the instruction list
349
     * @param idx
350
     *            the index in the instructions
351
     * @param callInst
352
     *            the method call to Unsafe
353
     * @throws IOException
354
     *             If any I/O error occur
355
     */
356
    private void patch_objectFieldOffset_Java8( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) throws IOException {
357
        UnsafeState state = findUnsafeState( instructions, idx );
1✔
358

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

363
        StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 1, callInst.getCodePosition() );
1✔
364
        WasmInstruction instr = stackValue.instr;
1✔
365
        WasmCallInstruction fieldInst = (WasmCallInstruction)instr;
1✔
366

367
        FunctionName fieldFuncName = fieldInst.getFunctionName();
1✔
368
        switch( fieldFuncName.signatureName ) {
1✔
369
            case "java/lang/Class.getDeclaredField(Ljava/lang/String;)Ljava/lang/reflect/Field;":
370
                stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, stackValue.idx ), 1, fieldInst.getCodePosition() );
1✔
371
                state.fieldName = ((WasmConstStringInstruction)stackValue.instr).getValue();
1✔
372

373
                // find the class value on which getDeclaredField is called
374
                stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, stackValue.idx ), 1, fieldInst.getCodePosition() );
1✔
375
                state.typeName = getClassConst( instructions, stackValue );
1✔
376
                break;
1✔
377

378
            default:
379
                throw new WasmException( "Unsupported Unsafe method to get target field: " + fieldFuncName.signatureName, -1 );
×
380
        }
381

382
        useFieldName( state );
1✔
383
        nop( instructions, from, idx + 2 );
1✔
384
    }
1✔
385

386
    /**
387
     * Patch a method call to Unsafe.objectFieldOffset() and find the parameter for other patch operations.
388
     * 
389
     * @param instructions
390
     *            the instruction list
391
     * @param idx
392
     *            the index in the instructions
393
     * @param callInst
394
     *            the method call to Unsafe
395
     * @param isAtomicReferenceFieldUpdater
396
     *            true, if is AtomicReferenceFieldUpdater
397
     * @throws IOException
398
     *             If any I/O error occur
399
     */
400
    private void patch_objectFieldOffset_Java11( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst, boolean isAtomicReferenceFieldUpdater ) throws IOException {
401
        UnsafeState state = findUnsafeState( instructions, idx );
1✔
402

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

407
        StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 1, callInst.getCodePosition() );
1✔
408
        state.fieldName = ((WasmConstStringInstruction)stackValue.instr).getValue();
1✔
409

410
        // find the class value on which getDeclaredField is called
411
        int classParamIdx = isAtomicReferenceFieldUpdater ? 3 : 2;
1✔
412
        stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, classParamIdx, callInst.getCodePosition() );
1✔
413
        state.typeName = getClassConst( instructions, stackValue );
1✔
414

415
        useFieldName( state );
1✔
416
        nop( instructions, from, idx + 2 );
1✔
417
    }
1✔
418

419
    /**
420
     * Patch a method call to Unsafe.arrayBaseOffset() and find the parameter for other patch operations.
421
     * 
422
     * @param instructions
423
     *            the instruction list
424
     * @param idx
425
     *            the index in the instructions
426
     * @param callInst
427
     *            the method call to Unsafe
428
     */
429
    private void patch_arrayBaseOffset( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) {
430
        UnsafeState state = findUnsafeState( instructions, idx );
×
431

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

436
        StackValue stackValue = StackInspector.findInstructionThatPushValue( paramInstructions, 1, callInst.getCodePosition() );
×
437
        state.typeName = getClassConst( instructions, stackValue );
×
438

439
        nop( instructions, from, idx );
×
440
        // we put the constant value 0 on the stack, we does not need array base offset in WASM
441
        instructions.set( idx, new WasmConstNumberInstruction( 0, callInst.getCodePosition(), callInst.getLineNumber() ) );
×
442
    }
×
443

444
    /**
445
     * Patch method call to Unsafe.arrayIndexScale()
446
     * 
447
     * @param instructions
448
     *            the instruction list
449
     * @param idx
450
     *            the index in the instructions
451
     * @param callInst
452
     *            the method call to Unsafe
453
     */
454
    private void patch_arrayIndexScale( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) {
455
        int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 2, callInst.getCodePosition() ).idx;
×
456

457
        nop( instructions, from, idx );
×
458
        // we put the constant value 1 on the stack because we does not want shift array positions
459
        instructions.set( idx, new WasmConstNumberInstruction( 1, callInst.getCodePosition(), callInst.getLineNumber() ) );
×
460
    }
×
461

462
    /**
463
     * Mark the field as used
464
     * 
465
     * @param state
466
     *            the state
467
     * @throws IOException
468
     *             If any I/O error occur
469
     */
470
    private void useFieldName( @Nonnull UnsafeState state ) throws IOException {
471
        FieldInfo fieldInfo = classFileLoader.get( state.typeName ).getField( state.fieldName );
1✔
472
        NamedStorageType fieldName = new NamedStorageType( state.typeName, fieldInfo, types );
1✔
473
        types.valueOf( state.typeName ).useFieldName( fieldName );
1✔
474
    }
1✔
475

476
    /**
477
     * Patch an unsafe function that access a field
478
     * 
479
     * @param instructions
480
     *            the instruction list
481
     * @param idx
482
     *            the index in the instructions
483
     * @param callInst
484
     *            the method call to Unsafe
485
     * @param name
486
     *            the calling function
487
     * @param fieldNameParam
488
     *            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.
489
     */
490
    private void patchFieldFunction( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) {
491
        StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() );
1✔
492
        WasmInstruction instr = stackValue.instr;
1✔
493

494
        Set<FunctionName> fieldNames;
495
        FunctionName fieldNameWithOffset = null;
1✔
496
        if( instr.getType() == Type.Global ) {
1✔
497
            fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName();
1✔
498
            fieldNames = Collections.singleton( fieldNameWithOffset );
1✔
499
        } else {
500
            // java.util.concurrent.ConcurrentHashMap.tabAt() calculate a value with the field
501
            fieldNames = new HashSet<>();
×
502
            int pos2 = stackValue.idx;
×
503
            stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam + 1, callInst.getCodePosition() );
×
504
            int i = stackValue.idx;
×
505
            for( ; i < pos2; i++ ) {
×
506
                instr = instructions.get( i );
×
507
                if( instr.getType() != Type.Global ) {
×
508
                    continue;
×
509
                }
510
                fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName();
×
511
                fieldNames.add( fieldNameWithOffset );
×
512
            }
513
        }
514

515
        WatCodeSyntheticFunctionName func =
1✔
516
                        new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + fieldNameWithOffset.methodName + '.' + name.methodName, name.signature, "", (AnyType[])null ) {
1✔
517
                            @Override
518
                            protected String getCode() {
519
                                UnsafeState state = null;
1✔
520
                                for(FunctionName fieldNameWithOffset : fieldNames ) {
1✔
521
                                    state = unsafes.get( fieldNameWithOffset );
1✔
522
                                    if( state != null ) {
1✔
523
                                        break;
1✔
524
                                    }
525
                                }
1✔
526
                                if( state == null ) {
1✔
527
                                    if( functions.isFinish() ) {
1✔
528
                                        throw new RuntimeException( this.fullName + name.signature );
×
529
                                    }
530
                                    // we are in the scan phase. The static code was not scanned yet.
531
                                    return "";
1✔
532
                                }
533
                                AnyType[] paramTypes = callInst.getPopValueTypes();
1✔
534
                                switch( name.methodName ) {
1✔
535
                                    case "compareAndSwapInt":
536
                                    case "compareAndSwapLong":
537
                                    case "compareAndSwapObject":
538
                                    case "compareAndSet": // AtomicReferenceFieldUpdater
539
                                        AnyType type = paramTypes[3];
1✔
540
                                        if( type.isRefType() ) {
1✔
541
                                            type = ValueType.ref;
1✔
542
                                        }
543
                                        if( state.fieldName != null ) {
1✔
544
                                            // field access
545
                                            int paramOffset = "java/util/concurrent/atomic/AtomicReferenceFieldUpdater".equals( name.className ) ? -1 : 0;
1✔
546
                                            return "local.get 1" // THIS
1✔
547
                                                            + " struct.get " + state.typeName + ' ' + state.fieldName //
548
                                                            + " local.get " + (3 + paramOffset) // expected
549
                                                            + " " + type + ".eq" //
550
                                                            + " if" //
551
                                                            + "   local.get 1" // THIS
552
                                                            + "   local.get " + (4 + paramOffset) // update
553
                                                            + "   struct.set " + state.typeName + ' ' + state.fieldName //
554
                                                            + "   i32.const 1" //
555
                                                            + "   return" //
556
                                                            + " end" //
557
                                                            + " i32.const 1" //
558
                                                            + " return";
559
                                        } else {
560
                                            // array access
561
                                            return "local.get 1" // THIS
×
562
                                                            + " local.get 2" // the array index
563
                                                            + " i32.wrap_i64" // long -> int
564
                                                            + " array.get " + state.typeName //
565
                                                            + " local.get 3 " // expected
566
                                                            + " " + type + ".eq" //
567
                                                            + " if" //
568
                                                            + "   local.get 1" // THIS
569
                                                            + "   local.get 2" // the array index
570
                                                            + "   i32.wrap_i64" // long -> int
571
                                                            + "   local.get 4 " // update
572
                                                            + "   array.set " + state.typeName //
573
                                                            + "   i32.const 1" //
574
                                                            + "   return" //
575
                                                            + " end" //
576
                                                            + " i32.const 1" //
577
                                                            + " return";
578
                                        }
579

580
                                    case "getAndAddInt":
581
                                    case "getAndAddLong":
582
                                        return "local.get 1" // THIS
1✔
583
                                                        + " local.get 1" // THIS
584
                                                        + " struct.get " + state.typeName + ' ' + state.fieldName //
585
                                                        + " local.tee 4" // temp
586
                                                        + " local.get 3 " // delta
587
                                                        + paramTypes[3] + ".add" //
588
                                                        + " struct.set " + state.typeName + ' ' + state.fieldName //
589
                                                        + " local.get 4" // temp
590
                                                        + " return";
591

592
                                    case "getAndSetInt":
593
                                    case "getAndSetLong":
594
                                    case "getAndSetObject":
595
                                        return "local.get 1" // THIS
1✔
596
                                                        + " struct.get " + state.typeName + ' ' + state.fieldName //
597
                                                        + " local.get 1" // THIS
598
                                                        + " local.get 3" // newValue
599
                                                        + " struct.set " + state.typeName + ' ' + state.fieldName //
600
                                                        + " return";
601

602
                                    case "putOrderedInt":
603
                                    case "putInt":
604
                                    case "putOrderedLong":
605
                                    case "putLong":
606
                                    case "putOrderedObject":
607
                                    case "putObjectVolatile":
608
                                    case "putObject":
609
                                        if( state.fieldName != null ) {
1✔
610
                                            // field access
611
                                            return "local.get 1" // THIS
1✔
612
                                                            + " local.get 3" // x
613
                                                            + " struct.set " + state.typeName + ' ' + state.fieldName;
614
                                        } else {
615
                                            // array access
616
                                            return "local.get 1" // THIS
×
617
                                                            + " local.get 2" // the array index
618
                                                            + " i32.wrap_i64" // long -> int
619
                                                            + " local.get 3" // x
620
                                                            + " array.set " + state.typeName;
621
                                        }
622

623
                                    case "getInt":
624
                                    case "getLong":
625
                                        return "local.get 1" // THIS
1✔
626
                                                        + " struct.get " + state.typeName + ' ' + state.fieldName //
627
                                                        + " return";
628

629
                                    case "getObjectVolatile":
630
                                        return "local.get 1" // array
×
631
                                                        + " local.get 2" // the array index
632
                                                        + " i32.wrap_i64" // long -> int
633
                                                        + " array.get " + state.typeName
634
                                                        + " return";
635
                                }
636

637
                                throw new RuntimeException( name.signatureName );
×
638
                            }
639
                        };
640
        boolean needThisParameter = true;
1✔
641
        functions.markAsNeeded( func, needThisParameter ); // original function has an THIS parameter of the Unsafe instance, we need to consume it
1✔
642
        WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), needThisParameter );
1✔
643
        instructions.set( idx, call );
1✔
644

645
        // a virtual method call has also a DUP of this because we need for virtual method dispatch the parameter 2 times.
646
        for( int i = idx; i >= 0; i-- ) {
1✔
647
            instr = instructions.get( i );
1✔
648
            if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) {
1✔
649
                nop( instructions, i, i + 1 );
1✔
650
                break;
1✔
651
            }
652
        }
653
    }
1✔
654

655
    /**
656
     * Patch an unsafe function that access a field
657
     * 
658
     * @param instructions
659
     *            the instruction list
660
     * @param idx
661
     *            the index in the instructions
662
     * @param callInst
663
     *            the method call to Unsafe
664
     */
665
    private void patch_getLongUnaligned( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, FunctionName name ) {
666
        WatCodeSyntheticFunctionName func = new WatCodeSyntheticFunctionName( "", name.methodName, name.signature, "unreachable", (AnyType[])null );
×
667
        functions.markAsNeeded( func, false );
×
668
        WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), false );
×
669
        instructions.set( idx, call );
×
670
    }
×
671

672
    /**
673
     * Patch an unsafe function that access a field
674
     * 
675
     * @param instructions
676
     *            the instruction list
677
     * @param idx
678
     *            the index in the instructions
679
     * @param callInst
680
     *            the method call to Unsafe
681
     */
682
    private void patch_isBigEndian( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst ) {
683
//        int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 1, callInst.getCodePosition() ).idx;
684
//
685
//        nop( instructions, from, idx );
686

687
        // on x86 use little endian
688
        instructions.set( idx, new WasmConstNumberInstruction( 0, callInst.getCodePosition(), callInst.getLineNumber() ) );
×
689
    }
×
690

691
    /**
692
     * Patch an unsafe function that access a field
693
     * 
694
     * @param instructions
695
     *            the instruction list
696
     * @param idx
697
     *            the index in the instructions
698
     * @param callInst
699
     *            the method call to Unsafe
700
     */
701
    private void replaceWithConstNumber( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, int paramCount, int number ) {
702
        int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 1, callInst.getCodePosition() ).idx;
×
703

704
        nop( instructions, from, idx );
×
705

706
        // on x86 use little endian
707
        instructions.set( idx, new WasmConstNumberInstruction( number, callInst.getCodePosition(), callInst.getLineNumber() ) );
×
708
    }
×
709

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

723
        nop( instructions, from, idx + 1 );
×
724
    }
×
725

726
    /**
727
     * Replace the instructions with NOP operations
728
     * 
729
     * @param instructions
730
     *            the instruction list
731
     * @param from
732
     *            starting index
733
     * @param to
734
     *            end index
735
     */
736
    private void nop( List<WasmInstruction> instructions, int from, int to ) {
737
        for( int i = from; i < to; i++ ) {
1✔
738
            WasmInstruction instr = instructions.get( i );
1✔
739
            instructions.set( i, new WasmNopInstruction( instr.getCodePosition(), instr.getLineNumber() ) );
1✔
740
        }
741
    }
1✔
742

743
    private void patchVarHandle( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) {
744
        FunctionName name = callInst.getFunctionName();
×
745
        switch( name.methodName ) {
×
746
            case "getAndSet":
747
                patchVarHandleFieldFunction( instructions, idx, callInst, name, 3 );
×
748
                break;
749
        }
750
    }
×
751

752
    /**
753
     * Patch an unsafe function that access a field
754
     * 
755
     * @param instructions
756
     *            the instruction list
757
     * @param idx
758
     *            the index in the instructions
759
     * @param callInst
760
     *            the method call to Unsafe
761
     * @param name
762
     *            the calling function
763
     * @param fieldNameParam
764
     *            the function parameter with the field offset on the stack. This must be a long (Java signature "J") for Unsafe.
765
     */
766
    private void patchVarHandleFieldFunction( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) {
767
        StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() );
×
768
        FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)stackValue.instr).getFieldName();
×
769
        WatCodeSyntheticFunctionName func =
×
770
                        new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + name.methodName, name.signature, "", (AnyType[])null ) {
×
771
                            @Override
772
                            protected String getCode() {
773
                                UnsafeState state = unsafes.get( fieldNameWithOffset );
×
774
                                if( state == null ) {
×
775
                                    // we are in the scan phase. The static code was not scanned yet.
776
                                    return "";
×
777
                                }
778
                                AnyType[] paramTypes = callInst.getPopValueTypes();
×
779
                                switch( name.methodName ) {
×
780
                                    case "getAndSet":
781
                                        return "local.get 1" // THIS
×
782
                                                        + " struct.get " + state.typeName + ' ' + state.fieldName //
783
                                                        + " local.get 1" // THIS
784
                                                        + " local.get 2" // newValue
785
                                                        + " struct.set " + state.typeName + ' ' + state.fieldName //
786
                                                        + " return";
787

788
                                }
789

790
                                throw new RuntimeException( name.signatureName );
×
791
                            }
792
                        };
793
        functions.markAsNeeded( func, false );
×
794
        WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), false );
×
795
        instructions.set( idx, call );
×
796

797
        // a virtual method call has also a DUP of this because we need for virtual method dispatch the parameter 2 times.
798
        for( int i = idx; i >= 0; i-- ) {
×
799
            WasmInstruction instr = instructions.get( i );
×
800
            if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) {
×
801
                nop( instructions, i, i + 1 );
×
802
                break;
×
803
            }
804
        }
805
    }
×
806

807
    /**
808
     * Hold the state from declaring of Unsafe address
809
     */
810
    static class UnsafeState {
1✔
811
        String fieldName;
812

813
        String typeName;
814
    }
815
}
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