mirror of
https://github.com/i-net-software/JWebAssembly.git
synced 2025-03-25 07:27:52 +01:00
Add UnsafeManager
This commit is contained in:
parent
8b20e21725
commit
86004bfb4c
@ -170,6 +170,10 @@ class FunctionManager {
|
|||||||
FunctionName markAsNeeded( @Nonnull FunctionName name, boolean needThisParameter ) {
|
FunctionName markAsNeeded( @Nonnull FunctionName name, boolean needThisParameter ) {
|
||||||
FunctionState state = getOrCreate( name );
|
FunctionState state = getOrCreate( name );
|
||||||
if( state.state == State.None ) {
|
if( state.state == State.None ) {
|
||||||
|
if( "sun/misc/Unsafe".equals( name.className ) ) {
|
||||||
|
// Unsafe method call will be replaces by the UnsafeManager
|
||||||
|
return name;
|
||||||
|
}
|
||||||
if( isFinish ) {
|
if( isFinish ) {
|
||||||
throw new WasmException( "Prepare was already finish: " + name.signatureName, -1 );
|
throw new WasmException( "Prepare was already finish: " + name.signatureName, -1 );
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ class JavaMethodWasmCodeBuilder extends WasmCodeBuilder {
|
|||||||
|
|
||||||
private BranchManager branchManager;
|
private BranchManager branchManager;
|
||||||
|
|
||||||
|
private UnsafeManager unsafeManager;
|
||||||
/**
|
/**
|
||||||
* Create an instance
|
* Create an instance
|
||||||
*
|
*
|
||||||
@ -67,6 +68,7 @@ class JavaMethodWasmCodeBuilder extends WasmCodeBuilder {
|
|||||||
void init( WasmOptions options, ClassFileLoader classFileLoader ) {
|
void init( WasmOptions options, ClassFileLoader classFileLoader ) {
|
||||||
super.init( options, classFileLoader );
|
super.init( options, classFileLoader );
|
||||||
this.branchManager = new BranchManager( options, getInstructions(), getLocalVariables() );
|
this.branchManager = new BranchManager( options, getInstructions(), getLocalVariables() );
|
||||||
|
this.unsafeManager = new UnsafeManager( options.functions );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -750,6 +752,7 @@ class JavaMethodWasmCodeBuilder extends WasmCodeBuilder {
|
|||||||
}
|
}
|
||||||
wide = false;
|
wide = false;
|
||||||
}
|
}
|
||||||
|
unsafeManager.replaceUnsafe( getInstructions() );
|
||||||
branchManager.calculate();
|
branchManager.calculate();
|
||||||
branchManager.handle( byteCode ); // add branch operations
|
branchManager.handle( byteCode ); // add branch operations
|
||||||
if( returnType != null && !isEndsWithReturn() ) {
|
if( returnType != null && !isEndsWithReturn() ) {
|
||||||
|
326
src/de/inetsoftware/jwebassembly/module/UnsafeManager.java
Normal file
326
src/de/inetsoftware/jwebassembly/module/UnsafeManager.java
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Volker Berlin (i-net software)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package de.inetsoftware.jwebassembly.module;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import de.inetsoftware.jwebassembly.WasmException;
|
||||||
|
import de.inetsoftware.jwebassembly.module.StackInspector.StackValue;
|
||||||
|
import de.inetsoftware.jwebassembly.module.WasmInstruction.Type;
|
||||||
|
import de.inetsoftware.jwebassembly.wasm.AnyType;
|
||||||
|
import de.inetsoftware.jwebassembly.wasm.VariableOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace Unsafe operations with simpler WASM operations which does not need reflections.
|
||||||
|
*
|
||||||
|
* In Java a typical Unsafe code look like:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* private static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||||
|
* private static final long FIELD_OFFSET = UNSAFE.objectFieldOffset(Foobar.class.getDeclaredField("field"));
|
||||||
|
*
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* UNSAFE.compareAndSwapInt(this, FIELD_OFFSET, expect, update);
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Because WASM does not support reflection the native code of UNSAFE can't simple replaced. That this manager convert
|
||||||
|
* this to the follow pseudo code in WASM:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <code>
|
||||||
|
* Foobar..compareAndSwapInt(this, FIELD_OFFSET, expect, update);
|
||||||
|
*
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* boolean .compareAndSwapInt(Object obj, long fieldOffset, int expect, int update ) {
|
||||||
|
* if( obj.field == expect ) {
|
||||||
|
* obj.field = update;
|
||||||
|
* return true;
|
||||||
|
* }
|
||||||
|
* return false;
|
||||||
|
* }
|
||||||
|
* </code>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author Volker Berlin
|
||||||
|
*/
|
||||||
|
class UnsafeManager {
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private final FunctionManager functions;
|
||||||
|
|
||||||
|
private final HashMap<FunctionName, UnsafeState> unsafes = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of the manager
|
||||||
|
*
|
||||||
|
* @param functions
|
||||||
|
* The function manager to register the synthetic functions.
|
||||||
|
*/
|
||||||
|
UnsafeManager( @Nonnull FunctionManager functions ) {
|
||||||
|
this.functions = functions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace any Unsafe API call with direct field access.
|
||||||
|
*
|
||||||
|
* @param instructions
|
||||||
|
* the instruction list of a function/method
|
||||||
|
*/
|
||||||
|
void replaceUnsafe( List<WasmInstruction> instructions ) {
|
||||||
|
// search for Unsafe function calls
|
||||||
|
for( int i = 0; i < instructions.size(); i++ ) {
|
||||||
|
WasmInstruction instr = instructions.get( i );
|
||||||
|
switch( instr.getType() ) {
|
||||||
|
case CallVirtual:
|
||||||
|
case Call:
|
||||||
|
WasmCallInstruction callInst = (WasmCallInstruction)instr;
|
||||||
|
if( "sun/misc/Unsafe".equals( callInst.getFunctionName().className ) ) {
|
||||||
|
patch( instructions, i, callInst );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch in the instruction list an Unsafe method call. It does not change the count of instructions.
|
||||||
|
*
|
||||||
|
* @param instructions
|
||||||
|
* the instruction list of a function/method
|
||||||
|
* @param idx
|
||||||
|
* the index in the instructions
|
||||||
|
* @param callInst
|
||||||
|
* the method call to Unsafe
|
||||||
|
*/
|
||||||
|
private void patch( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) {
|
||||||
|
FunctionName name = callInst.getFunctionName();
|
||||||
|
switch( name.signatureName ) {
|
||||||
|
case "sun/misc/Unsafe.getUnsafe()Lsun/misc/Unsafe;":
|
||||||
|
patch_getUnsafe( instructions, idx );
|
||||||
|
break;
|
||||||
|
case "sun/misc/Unsafe.objectFieldOffset(Ljava/lang/reflect/Field;)J":
|
||||||
|
patch_objectFieldOffset( instructions, idx, callInst );
|
||||||
|
break;
|
||||||
|
case "sun/misc/Unsafe.getAndAddInt(Ljava/lang/Object;JI)I":
|
||||||
|
case "sun/misc/Unsafe.getAndSetInt(Ljava/lang/Object;JI)I":
|
||||||
|
case "sun/misc/Unsafe.putOrderedInt(Ljava/lang/Object;JI)V":
|
||||||
|
patchFunction( instructions, idx, callInst, name, 2 );
|
||||||
|
break;
|
||||||
|
case "sun/misc/Unsafe.compareAndSwapInt(Ljava/lang/Object;JII)Z":
|
||||||
|
patchFunction( instructions, idx, callInst, name, 3 );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new WasmException( "Unsupported Unsafe method: " + name.signatureName, -1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace a call to Unsafe.getUnsafe() with a NOP operation.
|
||||||
|
*
|
||||||
|
* @param instructions
|
||||||
|
* the instruction list of a function/method
|
||||||
|
* @param idx
|
||||||
|
* the index in the instructions
|
||||||
|
*/
|
||||||
|
private void patch_getUnsafe( List<WasmInstruction> instructions, int idx ) {
|
||||||
|
WasmInstruction instr = instructions.get( idx + 1 );
|
||||||
|
|
||||||
|
FunctionName fieldName = ((WasmGlobalInstruction)instr).getFieldName();
|
||||||
|
unsafes.putIfAbsent( fieldName, new UnsafeState() );
|
||||||
|
|
||||||
|
nop( instructions, idx, idx + 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch a method call to Unsafe.objectFieldOffset() and find the parameter for other patch operations.
|
||||||
|
*
|
||||||
|
* @param instructions
|
||||||
|
* the instruction list
|
||||||
|
* @param idx
|
||||||
|
* the index in the instructions
|
||||||
|
* @param callInst
|
||||||
|
* the method call to Unsafe
|
||||||
|
*/
|
||||||
|
private void patch_objectFieldOffset( List<WasmInstruction> instructions, int idx, WasmCallInstruction callInst ) {
|
||||||
|
// find the field on which the offset is assign: long FIELD_OFFSET = UNSAFE.objectFieldOffset(...
|
||||||
|
WasmInstruction instr = instructions.get( idx + 1 );
|
||||||
|
FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)instr).getFieldName();
|
||||||
|
UnsafeState state = unsafes.get( fieldNameWithOffset );
|
||||||
|
if( state == null ) {
|
||||||
|
unsafes.put( fieldNameWithOffset, state = new UnsafeState() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectFieldOffset() has 2 parameters THIS(Unsafe) and a Field
|
||||||
|
int from = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 2, callInst.getCodePosition() ).idx;
|
||||||
|
|
||||||
|
StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), 1, callInst.getCodePosition() );
|
||||||
|
instr = stackValue.instr;
|
||||||
|
WasmCallInstruction fieldInst = (WasmCallInstruction)instr;
|
||||||
|
|
||||||
|
FunctionName fieldFuncName = fieldInst.getFunctionName();
|
||||||
|
switch( fieldFuncName.signatureName ) {
|
||||||
|
case "java/lang/Class.getDeclaredField(Ljava/lang/String;)Ljava/lang/reflect/Field;":
|
||||||
|
stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, stackValue.idx ), 1, fieldInst.getCodePosition() );
|
||||||
|
state.fieldName = ((WasmConstStringInstruction)stackValue.instr).getValue();
|
||||||
|
|
||||||
|
// find the class value on which getDeclaredField is called
|
||||||
|
stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, stackValue.idx ), 1, fieldInst.getCodePosition() );
|
||||||
|
instr = stackValue.instr;
|
||||||
|
switch( instr.getType() ) {
|
||||||
|
case Local:
|
||||||
|
int slot = ((WasmLocalInstruction)instr).getSlot();
|
||||||
|
for( int i = stackValue.idx - 1; i >= 0; i-- ) {
|
||||||
|
instr = instructions.get( i );
|
||||||
|
if( instr.getType() == Type.Local ) {
|
||||||
|
WasmLocalInstruction loadInstr = (WasmLocalInstruction)instr;
|
||||||
|
if( loadInstr.getSlot() == slot && loadInstr.getOperator() == VariableOperator.set ) {
|
||||||
|
stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, i ), 1, fieldInst.getCodePosition() );
|
||||||
|
instr = stackValue.instr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state.typeName = ((WasmConstClassInstruction)instr).getValue();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new WasmException( "Unsupported Unsafe method to get target field: " + fieldFuncName.signatureName, -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
nop( instructions, from, idx + 2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch an unsafe function that access a field
|
||||||
|
*
|
||||||
|
* @param instructions
|
||||||
|
* the instruction list
|
||||||
|
* @param idx
|
||||||
|
* the index in the instructions
|
||||||
|
* @param callInst
|
||||||
|
* the method call to Unsafe
|
||||||
|
* @param name
|
||||||
|
* the calling function
|
||||||
|
* @param fieldNameParam
|
||||||
|
* the function parameter with the field offset. This must be a long (Java signature "J"). The THIS
|
||||||
|
* parameter has the index 0.
|
||||||
|
*/
|
||||||
|
private void patchFunction( List<WasmInstruction> instructions, int idx, final WasmCallInstruction callInst, FunctionName name, int fieldNameParam ) {
|
||||||
|
StackValue stackValue = StackInspector.findInstructionThatPushValue( instructions.subList( 0, idx ), fieldNameParam, callInst.getCodePosition() );
|
||||||
|
FunctionName fieldNameWithOffset = ((WasmGlobalInstruction)stackValue.instr).getFieldName();
|
||||||
|
WatCodeSyntheticFunctionName func =
|
||||||
|
new WatCodeSyntheticFunctionName( fieldNameWithOffset.className, '.' + name.methodName, name.signature, "", (AnyType[])null ) {
|
||||||
|
@Override
|
||||||
|
protected String getCode() {
|
||||||
|
UnsafeState state = unsafes.get( fieldNameWithOffset );
|
||||||
|
if( state == null ) {
|
||||||
|
// we are in the scan phase. The static code was not scanned yet.
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
switch( name.methodName ) {
|
||||||
|
case "compareAndSwapInt":
|
||||||
|
return "local.get 0" // THIS
|
||||||
|
+ " struct.get " + state.typeName + ' ' + state.fieldName //
|
||||||
|
+ " local.get 2" // expected
|
||||||
|
+ " i32.eq" //
|
||||||
|
+ " if" //
|
||||||
|
+ " local.get 0" // THIS
|
||||||
|
+ " local.get 3" // update
|
||||||
|
+ " struct.set " + state.typeName + ' ' + state.fieldName //
|
||||||
|
+ " i32.const 1" //
|
||||||
|
+ " return" //
|
||||||
|
+ " end" //
|
||||||
|
+ " i32.const 1" //
|
||||||
|
+ " return";
|
||||||
|
|
||||||
|
case "getAndAddInt":
|
||||||
|
return "local.get 0" // THIS
|
||||||
|
+ " local.get 0" // THIS
|
||||||
|
+ " struct.get " + state.typeName + ' ' + state.fieldName //
|
||||||
|
+ " local.tee 3" // temp
|
||||||
|
+ " local.get 2" // delta
|
||||||
|
+ " i32.add" //
|
||||||
|
+ " struct.set " + state.typeName + ' ' + state.fieldName //
|
||||||
|
+ " local.get 3" // temp
|
||||||
|
+ " return";
|
||||||
|
|
||||||
|
case "getAndSetInt":
|
||||||
|
return "local.get 0" // THIS
|
||||||
|
+ " struct.get " + state.typeName + ' ' + state.fieldName //
|
||||||
|
+ " local.get 0" // THIS
|
||||||
|
+ " local.get 2" // newValue
|
||||||
|
+ " struct.set " + state.typeName + ' ' + state.fieldName //
|
||||||
|
+ " return";
|
||||||
|
|
||||||
|
case "putOrderedInt":
|
||||||
|
return "local.get 0" // THIS
|
||||||
|
+ " local.get 2" // x
|
||||||
|
+ " struct.set " + state.typeName + ' ' + state.fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException( name.signatureName );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
functions.markAsNeeded( func, false );
|
||||||
|
WasmCallInstruction call = new WasmCallInstruction( func, callInst.getCodePosition(), callInst.getLineNumber(), callInst.getTypeManager(), false );
|
||||||
|
instructions.set( idx, call );
|
||||||
|
|
||||||
|
// a virtual method call has also a DUP of this because we need for virtual method dispatch the parameter 2 times.
|
||||||
|
for( int i = idx; i >= 0; i-- ) {
|
||||||
|
WasmInstruction instr = instructions.get( i );
|
||||||
|
if( instr.getType() == Type.DupThis && ((DupThis)instr).getValue() == callInst ) {
|
||||||
|
nop( instructions, i, i + 1 );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the instructions with NOP operations
|
||||||
|
*
|
||||||
|
* @param instructions
|
||||||
|
* the instruction list
|
||||||
|
* @param from
|
||||||
|
* starting index
|
||||||
|
* @param to
|
||||||
|
* end index
|
||||||
|
*/
|
||||||
|
private void nop( List<WasmInstruction> instructions, int from, int to ) {
|
||||||
|
for( int i = from; i < to; i++ ) {
|
||||||
|
WasmInstruction instr = instructions.get( i );
|
||||||
|
instructions.set( i, new WasmNopInstruction( instr.getCodePosition(), instr.getLineNumber() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hold the state from declaring of Unsafe address
|
||||||
|
*/
|
||||||
|
static class UnsafeState {
|
||||||
|
String fieldName;
|
||||||
|
|
||||||
|
String typeName;
|
||||||
|
}
|
||||||
|
}
|
95
test/de/inetsoftware/jwebassembly/runtime/UnsafeTest.java
Normal file
95
test/de/inetsoftware/jwebassembly/runtime/UnsafeTest.java
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Volker Berlin (i-net software)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package de.inetsoftware.jwebassembly.runtime;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
import de.inetsoftware.jwebassembly.ScriptEngine;
|
||||||
|
import de.inetsoftware.jwebassembly.WasmRule;
|
||||||
|
import de.inetsoftware.jwebassembly.api.annotation.Export;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Volker Berlin
|
||||||
|
*/
|
||||||
|
public class UnsafeTest extends AbstractBaseTest {
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static WasmRule rule = new WasmRule( TestClass.class );
|
||||||
|
|
||||||
|
public UnsafeTest( ScriptEngine script, String method, Object[] params ) {
|
||||||
|
super( rule, script, method, params );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameters( name = "{0}-{1}" )
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
ArrayList<Object[]> list = new ArrayList<>();
|
||||||
|
for( ScriptEngine script : ScriptEngine.testEngines() ) {
|
||||||
|
addParam( list, script, "compareAndSwapInt" );
|
||||||
|
addParam( list, script, "getAndAddInt" );
|
||||||
|
addParam( list, script, "getAndSetInt" );
|
||||||
|
addParam( list, script, "lazySet" );
|
||||||
|
}
|
||||||
|
rule.setTestParameters( list );
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestClass {
|
||||||
|
|
||||||
|
@Export
|
||||||
|
static int compareAndSwapInt() {
|
||||||
|
AtomicInteger obj = new AtomicInteger();
|
||||||
|
if( obj.compareAndSet( 0, 25 ) ) {
|
||||||
|
return obj.get();
|
||||||
|
} else {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Export
|
||||||
|
static int getAndAddInt() {
|
||||||
|
AtomicInteger obj = new AtomicInteger();
|
||||||
|
obj.set( 13 );
|
||||||
|
if( obj.getAndAdd( 25 ) == 13 ) {
|
||||||
|
return obj.get();
|
||||||
|
} else {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Export
|
||||||
|
static int getAndSetInt() {
|
||||||
|
AtomicInteger obj = new AtomicInteger();
|
||||||
|
obj.set( 13 );
|
||||||
|
if( obj.getAndSet( 25 ) == 13 ) {
|
||||||
|
return obj.get();
|
||||||
|
} else {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Export
|
||||||
|
static int lazySet() {
|
||||||
|
AtomicInteger obj = new AtomicInteger();
|
||||||
|
obj.lazySet( 42 );
|
||||||
|
return obj.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user