diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ClassForNameTransformer.java b/classlib/src/main/java/org/teavm/classlib/impl/ClassForNameTransformer.java deleted file mode 100644 index bb59d5b2e..000000000 --- a/classlib/src/main/java/org/teavm/classlib/impl/ClassForNameTransformer.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2017 Alexey Andreev. - * - * 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 org.teavm.classlib.impl; - -import java.util.Arrays; -import org.teavm.common.DisjointSet; -import org.teavm.model.BasicBlock; -import org.teavm.model.ClassHierarchy; -import org.teavm.model.ClassHolder; -import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.ClassHolderTransformerContext; -import org.teavm.model.Instruction; -import org.teavm.model.MethodHolder; -import org.teavm.model.MethodReference; -import org.teavm.model.Program; -import org.teavm.model.ValueType; -import org.teavm.model.Variable; -import org.teavm.model.instructions.AssignInstruction; -import org.teavm.model.instructions.ClassConstantInstruction; -import org.teavm.model.instructions.InvocationType; -import org.teavm.model.instructions.InvokeInstruction; -import org.teavm.model.instructions.StringConstantInstruction; - -public class ClassForNameTransformer implements ClassHolderTransformer { - private static final MethodReference getNameMethod = new MethodReference(Class.class, "getName", String.class); - private static final MethodReference forNameMethod = new MethodReference(Class.class, "forName", String.class, - boolean.class, ClassLoader.class, Class.class); - private static final MethodReference forNameShortMethod = new MethodReference(Class.class, "forName", - String.class, Class.class); - private static final MethodReference initMethod = new MethodReference(Class.class, "initialize", void.class); - - @Override - public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { - for (MethodHolder method : cls.getMethods()) { - Program program = method.getProgram(); - if (program != null) { - transformProgram(program, context.getHierarchy()); - } - } - } - - private void transformProgram(Program program, ClassHierarchy hierarchy) { - if (!hasForNameCall(program)) { - return; - } - - DisjointSet varSet = new DisjointSet(); - for (int i = 0; i < program.variableCount(); i++) { - varSet.create(); - } - int[] nameIndexes = new int[program.variableCount()]; - String[] constants = new String[program.variableCount()]; - Arrays.fill(nameIndexes, -1); - - for (BasicBlock block : program.getBasicBlocks()) { - for (Instruction instruction : block) { - if (instruction instanceof InvokeInstruction) { - InvokeInstruction invoke = (InvokeInstruction) instruction; - if (invoke.getMethod().equals(getNameMethod)) { - if (invoke.getReceiver() != null) { - nameIndexes[invoke.getReceiver().getIndex()] = invoke.getInstance().getIndex(); - } - } - } else if (instruction instanceof StringConstantInstruction) { - StringConstantInstruction stringConstant = (StringConstantInstruction) instruction; - constants[stringConstant.getReceiver().getIndex()] = stringConstant.getConstant(); - } else if (instruction instanceof AssignInstruction) { - AssignInstruction assign = (AssignInstruction) instruction; - varSet.union(assign.getAssignee().getIndex(), assign.getReceiver().getIndex()); - } - } - } - - nameIndexes = Arrays.copyOf(nameIndexes, varSet.size()); - int[] nameRepresentatives = new int[nameIndexes.length]; - Arrays.fill(nameRepresentatives, -1); - String[] constantsByClasses = new String[varSet.size()]; - - for (int i = 0; i < program.variableCount(); i++) { - int varClass = varSet.find(i); - if (nameRepresentatives[varClass] < 0) { - nameRepresentatives[varClass] = i; - } - if (nameIndexes[i] >= 0) { - nameIndexes[varClass] = varSet.find(nameIndexes[i]); - } - - constantsByClasses[varClass] = constants[i]; - } - - for (BasicBlock block : program.getBasicBlocks()) { - for (Instruction instruction : block) { - if (!(instruction instanceof InvokeInstruction)) { - continue; - } - InvokeInstruction invoke = (InvokeInstruction) instruction; - - if (!invoke.getMethod().equals(forNameMethod) && !invoke.getMethod().equals(forNameShortMethod)) { - continue; - } - - Variable representative; - - int classNameIndex = invoke.getArguments().get(0).getIndex(); - int nameIndex = nameIndexes[classNameIndex]; - String constant = constantsByClasses[invoke.getArguments().get(0).getIndex()]; - if (nameIndex >= 0) { - representative = program.variableAt(nameRepresentatives[nameIndex]); - } else if (constant != null) { - if (hierarchy.getClassSource().get(constant) == null || !filterClassName(constant)) { - InvokeInstruction invokeException = new InvokeInstruction(); - invokeException.setType(InvocationType.SPECIAL); - invokeException.setMethod(new MethodReference(ExceptionHelpers.class, "classNotFound", - Class.class)); - invokeException.setReceiver(program.createVariable()); - invokeException.setLocation(invoke.getLocation()); - invoke.insertPrevious(invokeException); - representative = invokeException.getReceiver(); - } else { - ClassConstantInstruction classConstant = new ClassConstantInstruction(); - classConstant.setConstant(ValueType.object(constant)); - classConstant.setReceiver(program.createVariable()); - classConstant.setLocation(invoke.getLocation()); - invoke.insertPrevious(classConstant); - representative = classConstant.getReceiver(); - } - } else { - continue; - } - - InvokeInstruction initInvoke = new InvokeInstruction(); - initInvoke.setLocation(invoke.getLocation()); - initInvoke.setType(InvocationType.SPECIAL); - initInvoke.setMethod(initMethod); - initInvoke.setInstance(representative); - invoke.insertPrevious(initInvoke); - - if (invoke.getReceiver() == null) { - invoke.delete(); - } else { - AssignInstruction assign = new AssignInstruction(); - assign.setLocation(invoke.getLocation()); - assign.setAssignee(representative); - assign.setReceiver(invoke.getReceiver()); - invoke.replace(assign); - } - } - } - } - - private boolean hasForNameCall(Program program) { - for (BasicBlock block : program.getBasicBlocks()) { - for (Instruction instruction : block) { - if (!(instruction instanceof InvokeInstruction)) { - continue; - } - - InvokeInstruction invoke = (InvokeInstruction) instruction; - - if (invoke.getMethod().equals(forNameMethod) || invoke.getMethod().equals(forNameShortMethod)) { - return true; - } - } - } - - return false; - } - - private boolean filterClassName(String className) { - switch (className) { - // It's a hack for Kotlin. Kotlin full reflection library is too heavyweight for TeaVM. - // This optimization enables full reflection when there's Kotlin/JVM reflection library - // in the classpath, since Kotlin uses Class.forName() to check whether there's - // full reflection library presents. If program does not use full reflection, - // but build configuration includes kotlin-reflect artifact as a dependency, - // it gets into classpath, which allows this optimization to be applied. - case "kotlin.reflect.jvm.internal.ReflectionFactoryImpl": - return false; - } - return true; - } -} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index 7f3fd3c7c..e52e21497 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -28,6 +28,7 @@ import org.teavm.classlib.impl.currency.CurrenciesGenerator; import org.teavm.classlib.impl.currency.CurrencyHelper; import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor; import org.teavm.classlib.impl.record.ObjectMethodsSubstitutor; +import org.teavm.classlib.impl.reflection.ReflectionTransformer; import org.teavm.classlib.impl.tz.DateTimeZoneProvider; import org.teavm.classlib.impl.tz.DateTimeZoneProviderIntrinsic; import org.teavm.classlib.impl.tz.DateTimeZoneProviderPatch; @@ -94,7 +95,7 @@ public class JCLPlugin implements TeaVMPlugin { if (!isBootstrap()) { host.registerService(CLDRReader.class, CLDRReader.getInstance(host.getProperties(), host.getClassLoader())); - host.add(new ClassForNameTransformer()); + host.add(new ReflectionTransformer()); } host.add(new AnnotationDependencyListener()); diff --git a/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java b/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java index ac8ec1656..7c2ffe142 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/ReflectionDependencyListener.java @@ -43,8 +43,9 @@ import org.teavm.model.ValueType; public class ReflectionDependencyListener extends AbstractDependencyListener { private List reflectionSuppliers; - private MethodReference fieldGet = new MethodReference(Field.class, "get", Object.class, Object.class); - private MethodReference fieldSet = new MethodReference(Field.class, "set", Object.class, Object.class, void.class); + private MethodReference fieldGet = new MethodReference(Field.class, "getWithoutCheck", Object.class, Object.class); + private MethodReference fieldSet = new MethodReference(Field.class, "setWithoutCheck", Object.class, Object.class, + void.class); private MethodReference newInstance = new MethodReference(Constructor.class, "newInstance", Object[].class, Object.class); private MethodReference invokeMethod = new MethodReference(Method.class, "invoke", Object.class, Object[].class, diff --git a/classlib/src/main/java/org/teavm/classlib/impl/reflection/ReflectionTransformer.java b/classlib/src/main/java/org/teavm/classlib/impl/reflection/ReflectionTransformer.java new file mode 100644 index 000000000..e8277827f --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/reflection/ReflectionTransformer.java @@ -0,0 +1,524 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.impl.reflection; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.teavm.common.DisjointSet; +import org.teavm.model.AccessLevel; +import org.teavm.model.BasicBlock; +import org.teavm.model.ClassHierarchy; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldHolder; +import org.teavm.model.FieldReader; +import org.teavm.model.FieldReference; +import org.teavm.model.Instruction; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.emit.ProgramEmitter; +import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.ClassConstantInstruction; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.RaiseInstruction; +import org.teavm.model.instructions.StringConstantInstruction; +import org.teavm.model.optimization.UnreachableBasicBlockEliminator; +import org.teavm.model.util.ProgramUtils; + +public class ReflectionTransformer implements ClassHolderTransformer { + private static final MethodReference getNameMethod = new MethodReference(Class.class, "getName", String.class); + private static final MethodReference forNameMethod = new MethodReference(Class.class, "forName", String.class, + boolean.class, ClassLoader.class, Class.class); + private static final MethodReference forNameShortMethod = new MethodReference(Class.class, "forName", + String.class, Class.class); + private static final MethodReference newRefUpdaterMethod = new MethodReference(AtomicReferenceFieldUpdater.class, + "newUpdater", Class.class, Class.class, String.class, AtomicReferenceFieldUpdater.class); + private static final MethodReference newIntUpdaterMethod = new MethodReference(AtomicIntegerFieldUpdater.class, + "newUpdater", Class.class, String.class, AtomicIntegerFieldUpdater.class); + private static final MethodReference newLongUpdaterMethod = new MethodReference(AtomicLongFieldUpdater.class, + "newUpdater", Class.class, String.class, AtomicLongFieldUpdater.class); + private static final MethodReference initMethod = new MethodReference(Class.class, "initialize", void.class); + + private Map updaterClasses = new HashMap<>(); + + private boolean prepared; + private DisjointSet varSet; + private int[] nameRepresentatives; + private String[] stringConstantsByClasses; + private ValueType[] classConstantsByClasses; + private boolean hasTruncatedBlocks; + + @Override + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + for (MethodHolder method : cls.getMethods()) { + Program program = method.getProgram(); + if (program != null) { + transformProgram(program, context); + } + } + } + + private void transformProgram(Program program, ClassHolderTransformerContext context) { + for (BasicBlock block : program.getBasicBlocks()) { + for (Instruction instruction : block) { + if (!(instruction instanceof InvokeInstruction)) { + continue; + } + InvokeInstruction invoke = (InvokeInstruction) instruction; + + var method = invoke.getMethod(); + if (method.equals(forNameMethod) || method.equals(forNameShortMethod)) { + transformForName(program, invoke, context); + } else if (method.equals(newRefUpdaterMethod)) { + transformRefUpdater(program, invoke, context); + } else if (method.equals(newIntUpdaterMethod)) { + transformPrimitiveUpdater(program, invoke, + "java.util.concurrent.atomic.BaseAtomicIntegerFieldUpdater", ValueType.INTEGER, context); + } else if (method.equals(newLongUpdaterMethod)) { + transformPrimitiveUpdater(program, invoke, + "java.util.concurrent.atomic.BaseAtomicLongFieldUpdater", ValueType.LONG, context); + } + } + } + if (hasTruncatedBlocks) { + new UnreachableBasicBlockEliminator().optimize(program); + } + cleanup(); + } + + private void prepare(Program program) { + if (prepared) { + return; + } + prepared = true; + + varSet = new DisjointSet(); + for (int i = 0; i < program.variableCount(); i++) { + varSet.create(); + } + var nameIndexes = new int[program.variableCount()]; + var stringConstants = new String[program.variableCount()]; + var classConstants = new ValueType[program.variableCount()]; + Arrays.fill(nameIndexes, -1); + + for (BasicBlock block : program.getBasicBlocks()) { + for (Instruction instruction : block) { + if (instruction instanceof InvokeInstruction) { + InvokeInstruction invoke = (InvokeInstruction) instruction; + if (invoke.getMethod().equals(getNameMethod)) { + if (invoke.getReceiver() != null) { + nameIndexes[invoke.getReceiver().getIndex()] = invoke.getInstance().getIndex(); + } + } + } else if (instruction instanceof StringConstantInstruction) { + StringConstantInstruction stringConstant = (StringConstantInstruction) instruction; + stringConstants[stringConstant.getReceiver().getIndex()] = stringConstant.getConstant(); + } else if (instruction instanceof ClassConstantInstruction) { + var classConstant = (ClassConstantInstruction) instruction; + classConstants[classConstant.getReceiver().getIndex()] = classConstant.getConstant(); + } else if (instruction instanceof AssignInstruction) { + AssignInstruction assign = (AssignInstruction) instruction; + varSet.union(assign.getAssignee().getIndex(), assign.getReceiver().getIndex()); + } + } + } + + nameRepresentatives = new int[varSet.size()]; + Arrays.fill(nameRepresentatives, -1); + stringConstantsByClasses = new String[varSet.size()]; + classConstantsByClasses = new ValueType[varSet.size()]; + + for (int i = 0; i < program.variableCount(); i++) { + int varClass = varSet.find(i); + if (nameIndexes[i] >= 0) { + nameRepresentatives[varClass] = varSet.find(nameIndexes[i]); + } + + stringConstantsByClasses[varClass] = stringConstants[i]; + classConstantsByClasses[varClass] = classConstants[i]; + } + } + + private void cleanup() { + if (prepared) { + prepared = false; + hasTruncatedBlocks = false; + varSet = null; + nameRepresentatives = null; + stringConstantsByClasses = null; + classConstantsByClasses = null; + } + } + + private void transformForName(Program program, InvokeInstruction invoke, ClassHolderTransformerContext context) { + var hierarchy = context.getHierarchy(); + prepare(program); + + Variable representative; + + int classNameIndex = varSet.find(invoke.getArguments().get(0).getIndex()); + var nameIndex = nameRepresentatives[classNameIndex]; + String constant = stringConstantsByClasses[classNameIndex]; + if (nameIndex >= 0) { + representative = program.variableAt(nameIndex); + } else if (constant != null) { + if (hierarchy.getClassSource().get(constant) == null || !filterClassName(constant)) { + emitException(invoke, ClassNotFoundException.class); + return; + } else { + ClassConstantInstruction classConstant = new ClassConstantInstruction(); + classConstant.setConstant(ValueType.object(constant)); + classConstant.setReceiver(program.createVariable()); + classConstant.setLocation(invoke.getLocation()); + invoke.insertPrevious(classConstant); + representative = classConstant.getReceiver(); + } + } else { + return; + } + + InvokeInstruction initInvoke = new InvokeInstruction(); + initInvoke.setLocation(invoke.getLocation()); + initInvoke.setType(InvocationType.SPECIAL); + initInvoke.setMethod(initMethod); + initInvoke.setInstance(representative); + invoke.insertPrevious(initInvoke); + + if (invoke.getReceiver() == null) { + invoke.delete(); + } else { + AssignInstruction assign = new AssignInstruction(); + assign.setLocation(invoke.getLocation()); + assign.setAssignee(representative); + assign.setReceiver(invoke.getReceiver()); + invoke.replace(assign); + } + } + + private void transformRefUpdater(Program program, InvokeInstruction invoke, + ClassHolderTransformerContext context) { + prepare(program); + + var targetTypeConstant = classConstantsByClasses[varSet.find(invoke.getArguments().get(0).getIndex())]; + var varTypeConstant = classConstantsByClasses[varSet.find(invoke.getArguments().get(1).getIndex())]; + var nameConstant = stringConstantsByClasses[varSet.find(invoke.getArguments().get(2).getIndex())]; + + if (targetTypeConstant == null || varTypeConstant == null || nameConstant == null) { + return; + } + + if (!(targetTypeConstant instanceof ValueType.Object)) { + emitException(invoke, IllegalArgumentException.class); + return; + } + + var className = ((ValueType.Object) targetTypeConstant).getClassName(); + var cls = context.getHierarchy().getClassSource().get(className); + if (cls == null) { + emitException(invoke, NoClassDefFoundError.class); + return; + } + + var field = cls.getField(nameConstant); + if (field == null) { + emitException(invoke, RuntimeException.class, NoSuchFieldException.class); + return; + } + + if (!field.getType().equals(varTypeConstant)) { + emitException(invoke, ClassCastException.class); + return; + } + + if (!field.hasModifier(ElementModifier.VOLATILE) || varTypeConstant instanceof ValueType.Primitive + || varTypeConstant == ValueType.VOID || field.hasModifier(ElementModifier.STATIC)) { + emitException(invoke, IllegalArgumentException.class); + return; + } + + var updaterClassName = getRefUpdaterClass(context, field); + var getField = new GetFieldInstruction(); + getField.setField(new FieldReference(updaterClassName, "INSTANCE")); + getField.setFieldType(ValueType.object(updaterClassName)); + getField.setLocation(invoke.getLocation()); + getField.setReceiver(invoke.getReceiver()); + invoke.replace(getField); + } + + private String getRefUpdaterClass(ClassHolderTransformerContext context, FieldReader field) { + var key = field.getReference().toString(); + return updaterClasses.computeIfAbsent(key, k -> createRefUpdaterClass(context, field)); + } + + private String createRefUpdaterClass(ClassHolderTransformerContext context, FieldReader field) { + var className = field.getOwnerName() + "$" + field.getName() + "$_AtomicUpdater$"; + var updaterClass = new ClassHolder(className); + updaterClass.setLevel(AccessLevel.PUBLIC); + updaterClass.setParent("java.util.concurrent.atomic.BaseAtomicReferenceFieldUpdater"); + fillClass(updaterClass, field.getOwnerName(), context.getHierarchy()); + updaterClass.addMethod(createGetRefMethod(field, className, context.getHierarchy())); + updaterClass.addMethod(createSetRefMethod(field, className, context.getHierarchy())); + context.submit(updaterClass); + return className; + } + + private MethodHolder createGetRefMethod(FieldReader field, String className, ClassHierarchy hierarchy) { + var method = new MethodHolder("get", ValueType.object("java.lang.Object"), + ValueType.object("java.lang.Object")); + method.setLevel(AccessLevel.PUBLIC); + + var pe = ProgramEmitter.create(method, hierarchy); + var instance = pe.var(1, Object.class); + pe.invoke(className, "check", ValueType.object(field.getOwnerName()), instance) + .getField(field.getName(), field.getType()) + .returnValue(); + + return method; + } + + private MethodHolder createSetRefMethod(FieldReader field, String className, ClassHierarchy hierarchy) { + var method = new MethodHolder("set", ValueType.object("java.lang.Object"), + ValueType.object("java.lang.Object"), ValueType.VOID); + method.setLevel(AccessLevel.PUBLIC); + + var pe = ProgramEmitter.create(method, hierarchy); + var instance = pe.var(1, Object.class); + var value = pe.var(2, Object.class); + pe.invoke(className, "check", ValueType.object(field.getOwnerName()), instance) + .setField(field.getName(), value.cast(field.getType())); + pe.exit(); + + return method; + } + + private void transformPrimitiveUpdater(Program program, InvokeInstruction invoke, String superclass, + ValueType primitiveType, ClassHolderTransformerContext context) { + prepare(program); + + var targetTypeConstant = classConstantsByClasses[varSet.find(invoke.getArguments().get(0).getIndex())]; + var nameConstant = stringConstantsByClasses[varSet.find(invoke.getArguments().get(1).getIndex())]; + + if (targetTypeConstant == null || nameConstant == null) { + return; + } + + if (!(targetTypeConstant instanceof ValueType.Object)) { + emitException(invoke, IllegalArgumentException.class); + return; + } + + var className = ((ValueType.Object) targetTypeConstant).getClassName(); + var cls = context.getHierarchy().getClassSource().get(className); + if (cls == null) { + emitException(invoke, NoClassDefFoundError.class); + return; + } + + var field = cls.getField(nameConstant); + if (field == null) { + emitException(invoke, RuntimeException.class, NoSuchFieldException.class); + return; + } + + if (!field.hasModifier(ElementModifier.VOLATILE) || field.hasModifier(ElementModifier.STATIC) + || !field.getType().equals(primitiveType)) { + emitException(invoke, IllegalArgumentException.class); + return; + } + + var updaterClassName = getPrimitiveUpdaterClass(context, field, superclass); + var getField = new GetFieldInstruction(); + getField.setField(new FieldReference(updaterClassName, "INSTANCE")); + getField.setFieldType(ValueType.object(updaterClassName)); + getField.setLocation(invoke.getLocation()); + getField.setReceiver(invoke.getReceiver()); + invoke.replace(getField); + } + + private String getPrimitiveUpdaterClass(ClassHolderTransformerContext context, FieldReader field, + String superclass) { + var key = field.getReference().toString(); + return updaterClasses.computeIfAbsent(key, k -> createPrimitiveUpdaterClass(context, field, superclass)); + } + + private String createPrimitiveUpdaterClass(ClassHolderTransformerContext context, FieldReader field, + String superclass) { + var className = field.getOwnerName() + "$" + field.getName() + "$_AtomicUpdater$"; + var updaterClass = new ClassHolder(className); + updaterClass.setLevel(AccessLevel.PUBLIC); + updaterClass.setParent(superclass); + fillClass(updaterClass, field.getOwnerName(), context.getHierarchy()); + updaterClass.addMethod(createGetPrimitiveMethod(field, className, context.getHierarchy())); + updaterClass.addMethod(createSetPrimitiveMethod(field, className, context.getHierarchy())); + context.submit(updaterClass); + return className; + } + + private MethodHolder createGetPrimitiveMethod(FieldReader field, String className, ClassHierarchy hierarchy) { + var method = new MethodHolder("get", ValueType.object("java.lang.Object"), field.getType()); + method.setLevel(AccessLevel.PUBLIC); + + var pe = ProgramEmitter.create(method, hierarchy); + var instance = pe.var(1, Object.class); + pe.invoke(className, "check", ValueType.object(field.getOwnerName()), instance) + .getField(field.getName(), field.getType()) + .returnValue(); + + return method; + } + + private MethodHolder createSetPrimitiveMethod(FieldReader field, String className, ClassHierarchy hierarchy) { + var method = new MethodHolder("set", ValueType.object("java.lang.Object"), field.getType(), ValueType.VOID); + method.setLevel(AccessLevel.PUBLIC); + + var pe = ProgramEmitter.create(method, hierarchy); + var instance = pe.var(1, Object.class); + var value = pe.var(2, field.getType()); + pe.invoke(className, "check", ValueType.object(field.getOwnerName()), instance) + .setField(field.getName(), value); + pe.exit(); + + return method; + } + + private void fillClass(ClassHolder cls, String targetClassName, ClassHierarchy hierarchy) { + var instanceField = new FieldHolder("INSTANCE"); + instanceField.setType(ValueType.object(cls.getName())); + instanceField.setLevel(AccessLevel.PUBLIC); + instanceField.getModifiers().add(ElementModifier.STATIC); + cls.addField(instanceField); + + cls.addMethod(createConstructor(cls, hierarchy)); + cls.addMethod(createInitializer(cls, hierarchy)); + cls.addMethod(createCheck(targetClassName, hierarchy)); + } + + private MethodHolder createConstructor(ClassHolder cls, ClassHierarchy hierarchy) { + var ctor = new MethodHolder("", ValueType.VOID); + ctor.setLevel(AccessLevel.PRIVATE); + + var pe = ProgramEmitter.create(ctor, hierarchy); + pe.var(0, AtomicReferenceFieldUpdater.class).invokeSpecial(cls.getParent(), "", ValueType.VOID); + pe.exit(); + + return ctor; + } + + private MethodHolder createInitializer(ClassHolder cls, ClassHierarchy hierarchy) { + var initializer = new MethodHolder("", ValueType.VOID); + initializer.setLevel(AccessLevel.PRIVATE); + initializer.getModifiers().add(ElementModifier.STATIC); + + var pe = ProgramEmitter.create(initializer, hierarchy); + pe.setField(cls.getName(), "INSTANCE", pe.construct(cls.getName())); + pe.exit(); + + return initializer; + } + + private MethodHolder createCheck(String targetClassName, ClassHierarchy hierarchy) { + var method = new MethodHolder("check", ValueType.object("java.lang.Object"), + ValueType.object(targetClassName)); + method.setLevel(AccessLevel.PRIVATE); + method.getModifiers().add(ElementModifier.STATIC); + + var pe = ProgramEmitter.create(method, hierarchy); + var instance = pe.var(1, ValueType.object("java.lang.Object")); + pe.when(instance.isNull()).thenDo(() -> { + pe.construct(ClassCastException.class).raise(); + }); + instance.cast(ValueType.object(targetClassName)).returnValue(); + + return method; + } + + private void emitException(Instruction instruction, Class exceptionType) { + emitException(instruction, exceptionType, null); + } + + private void emitException(Instruction instruction, Class exceptionType, Class wrappedExceptionType) { + hasTruncatedBlocks = true; + ProgramUtils.truncateBlock(instruction); + + var program = instruction.getProgram(); + var block = instruction.getBasicBlock(); + + var construct = new ConstructInstruction(); + construct.setType(exceptionType.getName()); + construct.setReceiver(program.createVariable()); + construct.setLocation(instruction.getLocation()); + block.add(construct); + + var init = new InvokeInstruction(); + init.setType(InvocationType.SPECIAL); + init.setInstance(construct.getReceiver()); + + if (wrappedExceptionType != null) { + var wrappedConstruct = new ConstructInstruction(); + wrappedConstruct.setType(wrappedExceptionType.getName()); + wrappedConstruct.setReceiver(program.createVariable()); + wrappedConstruct.setLocation(instruction.getLocation()); + block.add(wrappedConstruct); + + var wrappedInit = new InvokeInstruction(); + wrappedInit.setType(InvocationType.SPECIAL); + wrappedInit.setInstance(wrappedConstruct.getReceiver()); + wrappedInit.setMethod(new MethodReference(wrappedExceptionType, "", void.class)); + wrappedInit.setLocation(instruction.getLocation()); + block.add(wrappedInit); + + init.setMethod(new MethodReference(exceptionType, "", Throwable.class, void.class)); + init.setArguments(wrappedConstruct.getReceiver()); + } else { + init.setMethod(new MethodReference(exceptionType, "", void.class)); + } + init.setLocation(instruction.getLocation()); + block.add(init); + + var raise = new RaiseInstruction(); + raise.setException(construct.getReceiver()); + raise.setLocation(instruction.getLocation()); + block.add(raise); + + instruction.delete(); + } + + private boolean filterClassName(String className) { + switch (className) { + // It's a hack for Kotlin. Kotlin full reflection library is too heavyweight for TeaVM. + // This optimization enables full reflection when there's Kotlin/JVM reflection library + // in the classpath, since Kotlin uses Class.forName() to check whether there's + // full reflection library presents. If program does not use full reflection, + // but build configuration includes kotlin-reflect artifact as a dependency, + // it gets into classpath, which allows this optimization to be applied. + case "kotlin.reflect.jvm.internal.ReflectionFactoryImpl": + return false; + } + return true; + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index d3d638e38..670797d1f 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -354,19 +354,19 @@ public class TClass extends TObject implements TAnnotatedElement, TType { return fields.clone(); } - public TField getDeclaredField(String name) throws TNoSuchFieldError { + public TField getDeclaredField(String name) throws TNoSuchFieldException { for (TField field : getDeclaredFields()) { if (field.getName().equals(name)) { return field; } } - throw new TNoSuchFieldError(); + throw new TNoSuchFieldException(); } - public TField getField(String name) throws TNoSuchFieldError { + public TField getField(String name) throws TNoSuchFieldException { TField result = findField(name, new HashSet<>()); if (result == null) { - throw new TNoSuchFieldError(); + throw new TNoSuchFieldException(); } return result; } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TNoSuchFieldException.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TNoSuchFieldException.java new file mode 100644 index 000000000..0f8bda427 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TNoSuchFieldException.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * 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 org.teavm.classlib.java.lang; + +public class TNoSuchFieldException extends TReflectiveOperationException { + public TNoSuchFieldException() { + super(); + } + + public TNoSuchFieldException(String message) { + super(message); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TField.java b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TField.java index c0ef731e9..22edba0a2 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TField.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/reflect/TField.java @@ -86,19 +86,23 @@ public class TField extends TAccessibleObject implements TMember { } public Object get(Object obj) throws TIllegalArgumentException, TIllegalAccessException { - if (getter == null) { - throw new TIllegalAccessException(); - } + checkGetAccess(); checkInstance(obj); + return getWithoutCheck(obj); + } + + public Object getWithoutCheck(Object obj) { PlatformObject result = getter.get(Platform.getPlatformObject(obj)); return Converter.toJava(result); } public void set(Object obj, Object value) throws TIllegalArgumentException, TIllegalAccessException { - if (setter == null) { - throw new TIllegalAccessException(); - } + checkSetAccess(); checkInstance(obj); + setWithoutCheck(obj, value); + } + + public void setWithoutCheck(Object obj, Object value) { setter.set(Platform.getPlatformObject(obj), Converter.fromJava(value)); } @@ -112,4 +116,16 @@ public class TField extends TAccessibleObject implements TMember { } } } + + public void checkGetAccess() throws TIllegalAccessException { + if (getter == null) { + throw new TIllegalAccessException(); + } + } + + public void checkSetAccess() throws TIllegalAccessException { + if (setter == null) { + throw new TIllegalAccessException(); + } + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TAtomicIntegerFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TAtomicIntegerFieldUpdater.java new file mode 100644 index 000000000..b1a84f39c --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TAtomicIntegerFieldUpdater.java @@ -0,0 +1,138 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import java.lang.reflect.Modifier; +import java.util.function.IntBinaryOperator; +import java.util.function.IntUnaryOperator; +import org.teavm.classlib.java.lang.TClass; +import org.teavm.classlib.java.lang.TIllegalAccessException; +import org.teavm.classlib.java.lang.TNoSuchFieldException; + +public abstract class TAtomicIntegerFieldUpdater { + protected TAtomicIntegerFieldUpdater() { + } + + @SuppressWarnings("EqualsBetweenInconvertibleTypes") + public static TAtomicIntegerFieldUpdater newUpdater(TClass tclass, String fieldName) { + try { + var field = tclass.getDeclaredField(fieldName); + if (!Modifier.isVolatile(field.getModifiers()) || Modifier.isStatic(field.getModifiers()) + || !field.getType().equals(int.class)) { + throw new IllegalArgumentException(); + } else { + field.checkGetAccess(); + field.checkSetAccess(); + return new TReflectionBasedAtomicIntegerFieldUpdater<>(field); + } + } catch (TNoSuchFieldException | TIllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public abstract boolean compareAndSet(T obj, int expect, int update); + + public abstract boolean weakCompareAndSet(T obj, int expect, int update); + + public abstract void set(T obj, int newValue); + + public abstract void lazySet(T obj, int newValue); + + public abstract int get(T obj); + + public int getAndSet(T obj, int newValue) { + while (true) { + var currentValue = get(obj); + if (compareAndSet(obj, currentValue, newValue)) { + return currentValue; + } + } + } + + public int getAndIncrement(T obj) { + return getAndAdd(obj, 1); + } + + public int getAndDecrement(T obj) { + return getAndAdd(obj, -1); + } + + public int getAndAdd(T obj, int delta) { + while (true) { + var currentValue = get(obj); + if (compareAndSet(obj, currentValue, currentValue + delta)) { + return currentValue; + } + } + } + + public int incrementAndGet(T obj) { + return addAndGet(obj, 1); + } + + public int decrementAndGet(T obj) { + return addAndGet(obj, -1); + } + + public int addAndGet(T obj, int delta) { + while (true) { + var currentValue = get(obj); + if (compareAndSet(obj, currentValue, currentValue + delta)) { + return currentValue + delta; + } + } + } + + public final int getAndUpdate(T obj, IntUnaryOperator updateFunction) { + while (true) { + var currentValue = get(obj); + var newValue = updateFunction.applyAsInt(currentValue); + if (compareAndSet(obj, currentValue, newValue)) { + return currentValue; + } + } + } + + public final int updateAndGet(T obj, IntUnaryOperator updateFunction) { + while (true) { + var currentValue = get(obj); + var newValue = updateFunction.applyAsInt(currentValue); + if (compareAndSet(obj, currentValue, newValue)) { + return newValue; + } + } + } + + public final int getAndAccumulate(T obj, int x, IntBinaryOperator accumulatorFunction) { + while (true) { + var currentValue = get(obj); + var newValue = accumulatorFunction.applyAsInt(currentValue, x); + if (compareAndSet(obj, currentValue, newValue)) { + return currentValue; + } + } + } + + public final int accumulateAndGet(T obj, int x, IntBinaryOperator accumulatorFunction) { + while (true) { + var currentValue = get(obj); + var newValue = accumulatorFunction.applyAsInt(currentValue, x); + if (compareAndSet(obj, currentValue, newValue)) { + return newValue; + } + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TAtomicLongFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TAtomicLongFieldUpdater.java new file mode 100644 index 000000000..a2583b3ee --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TAtomicLongFieldUpdater.java @@ -0,0 +1,138 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import java.lang.reflect.Modifier; +import java.util.function.LongBinaryOperator; +import java.util.function.LongUnaryOperator; +import org.teavm.classlib.java.lang.TClass; +import org.teavm.classlib.java.lang.TIllegalAccessException; +import org.teavm.classlib.java.lang.TNoSuchFieldException; + +public abstract class TAtomicLongFieldUpdater { + protected TAtomicLongFieldUpdater() { + } + + @SuppressWarnings("EqualsBetweenInconvertibleTypes") + public static TAtomicLongFieldUpdater newUpdater(TClass tclass, String fieldName) { + try { + var field = tclass.getDeclaredField(fieldName); + if (!Modifier.isVolatile(field.getModifiers()) || Modifier.isStatic(field.getModifiers()) + || !field.getType().equals(long.class)) { + throw new IllegalArgumentException(); + } else { + field.checkGetAccess(); + field.checkSetAccess(); + return new TReflectionBasedAtomicLongFieldUpdater<>(field); + } + } catch (TNoSuchFieldException | TIllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public abstract boolean compareAndSet(T obj, long expect, long update); + + public abstract boolean weakCompareAndSet(T obj, long expect, long update); + + public abstract void set(T obj, long newValue); + + public abstract void lazySet(T obj, long newValue); + + public abstract long get(T obj); + + public long getAndSet(T obj, long newValue) { + while (true) { + var currentValue = get(obj); + if (compareAndSet(obj, currentValue, newValue)) { + return currentValue; + } + } + } + + public long getAndIncrement(T obj) { + return getAndAdd(obj, 1); + } + + public long getAndDecrement(T obj) { + return getAndAdd(obj, -1); + } + + public long getAndAdd(T obj, long delta) { + while (true) { + var currentValue = get(obj); + if (compareAndSet(obj, currentValue, currentValue + delta)) { + return currentValue; + } + } + } + + public long incrementAndGet(T obj) { + return addAndGet(obj, 1); + } + + public long decrementAndGet(T obj) { + return addAndGet(obj, -1); + } + + public long addAndGet(T obj, long delta) { + while (true) { + var currentValue = get(obj); + if (compareAndSet(obj, currentValue, currentValue + delta)) { + return currentValue + delta; + } + } + } + + public final long getAndUpdate(T obj, LongUnaryOperator updateFunction) { + while (true) { + var currentValue = get(obj); + var newValue = updateFunction.applyAsLong(currentValue); + if (compareAndSet(obj, currentValue, newValue)) { + return currentValue; + } + } + } + + public final long updateAndGet(T obj, LongUnaryOperator updateFunction) { + while (true) { + var currentValue = get(obj); + var newValue = updateFunction.applyAsLong(currentValue); + if (compareAndSet(obj, currentValue, newValue)) { + return newValue; + } + } + } + + public final long getAndAccumulate(T obj, long x, LongBinaryOperator accumulatorFunction) { + while (true) { + var currentValue = get(obj); + var newValue = accumulatorFunction.applyAsLong(currentValue, x); + if (compareAndSet(obj, currentValue, newValue)) { + return currentValue; + } + } + } + + public final long accumulateAndGet(T obj, long x, LongBinaryOperator accumulatorFunction) { + while (true) { + var currentValue = get(obj); + var newValue = accumulatorFunction.applyAsLong(currentValue, x); + if (compareAndSet(obj, currentValue, newValue)) { + return newValue; + } + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TAtomicReferenceFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TAtomicReferenceFieldUpdater.java new file mode 100644 index 000000000..51e2dd695 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TAtomicReferenceFieldUpdater.java @@ -0,0 +1,107 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import java.lang.reflect.Modifier; +import java.util.function.BinaryOperator; +import java.util.function.UnaryOperator; +import org.teavm.classlib.java.lang.TClass; +import org.teavm.classlib.java.lang.TIllegalAccessException; +import org.teavm.classlib.java.lang.TNoSuchFieldException; + +public abstract class TAtomicReferenceFieldUpdater { + protected TAtomicReferenceFieldUpdater() { + } + + public static TAtomicReferenceFieldUpdater newUpdater(TClass tclass, TClass vclass, + String fieldName) { + try { + var field = tclass.getDeclaredField(fieldName); + if (field.getType() != vclass) { + throw new ClassCastException(); + } + if (!Modifier.isVolatile(field.getModifiers()) || Modifier.isStatic(field.getModifiers()) + || field.getType().isPrimitive()) { + throw new IllegalArgumentException(); + } else { + field.checkGetAccess(); + field.checkSetAccess(); + return new TReflectionBasedAtomicReferenceFieldUpdater<>(field); + } + } catch (TNoSuchFieldException | TIllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public abstract boolean compareAndSet(T obj, V expect, V update); + + public abstract boolean weakCompareAndSet(T obj, V expect, V update); + + public abstract void set(T obj, V newValue); + + public abstract void lazySet(T obj, V newValue); + + public abstract V get(T obj); + + public V getAndSet(T obj, V newValue) { + while (true) { + var currentValue = get(obj); + if (compareAndSet(obj, currentValue, newValue)) { + return currentValue; + } + } + } + + public final V getAndUpdate(T obj, UnaryOperator updateFunction) { + while (true) { + var currentValue = get(obj); + var newValue = updateFunction.apply(currentValue); + if (compareAndSet(obj, currentValue, newValue)) { + return currentValue; + } + } + } + + public final V updateAndGet(T obj, UnaryOperator updateFunction) { + while (true) { + var currentValue = get(obj); + var newValue = updateFunction.apply(currentValue); + if (compareAndSet(obj, currentValue, newValue)) { + return newValue; + } + } + } + + public final V getAndAccumulate(T obj, V x, BinaryOperator accumulatorFunction) { + while (true) { + var currentValue = get(obj); + var newValue = accumulatorFunction.apply(currentValue, x); + if (compareAndSet(obj, currentValue, newValue)) { + return currentValue; + } + } + } + + public final V accumulateAndGet(T obj, V x, BinaryOperator accumulatorFunction) { + while (true) { + var currentValue = get(obj); + var newValue = accumulatorFunction.apply(currentValue, x); + if (compareAndSet(obj, currentValue, newValue)) { + return newValue; + } + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TBaseAtomicIntegerFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TBaseAtomicIntegerFieldUpdater.java new file mode 100644 index 000000000..46d4d01df --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TBaseAtomicIntegerFieldUpdater.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +abstract class TBaseAtomicIntegerFieldUpdater extends TAtomicIntegerFieldUpdater { + @Override + public boolean compareAndSet(T obj, int expect, int update) { + if (get(obj) != expect) { + return false; + } + set(obj, update); + return true; + } + + @Override + public boolean weakCompareAndSet(T obj, int expect, int update) { + return compareAndSet(obj, expect, update); + } + + @Override + public void lazySet(T obj, int newValue) { + set(obj, newValue); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TBaseAtomicLongFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TBaseAtomicLongFieldUpdater.java new file mode 100644 index 000000000..1ab1df742 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TBaseAtomicLongFieldUpdater.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +abstract class TBaseAtomicLongFieldUpdater extends TAtomicLongFieldUpdater { + @Override + public boolean compareAndSet(T obj, long expect, long update) { + if (get(obj) != expect) { + return false; + } + set(obj, update); + return true; + } + + @Override + public boolean weakCompareAndSet(T obj, long expect, long update) { + return compareAndSet(obj, expect, update); + } + + @Override + public void lazySet(T obj, long newValue) { + set(obj, newValue); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TBaseAtomicReferenceFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TBaseAtomicReferenceFieldUpdater.java new file mode 100644 index 000000000..9588a965a --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TBaseAtomicReferenceFieldUpdater.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +abstract class TBaseAtomicReferenceFieldUpdater extends TAtomicReferenceFieldUpdater { + @Override + public boolean compareAndSet(T obj, V expect, V update) { + if (get(obj) != expect) { + return false; + } + set(obj, update); + return true; + } + + @Override + public boolean weakCompareAndSet(T obj, V expect, V update) { + return compareAndSet(obj, expect, update); + } + + @Override + public void lazySet(T obj, V newValue) { + set(obj, newValue); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TLongOptimizedAtomicReferenceFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TLongOptimizedAtomicReferenceFieldUpdater.java new file mode 100644 index 000000000..8755868a2 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TLongOptimizedAtomicReferenceFieldUpdater.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +abstract class TLongOptimizedAtomicReferenceFieldUpdater + extends TBaseAtomicReferenceFieldUpdater { + @Override + public boolean compareAndSet(T obj, Long expect, Long update) { + if (getAsLong(obj) != expect) { + return false; + } + set(obj, update.longValue()); + return true; + } + + @Override + public void set(T obj, Long newValue) { + set(obj, newValue.longValue()); + } + + @Override + public Long get(T obj) { + return getAsLong(obj); + } + + abstract long getAsLong(T obj); + + abstract void set(T obj, long value); +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TReflectionBasedAtomicIntegerFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TReflectionBasedAtomicIntegerFieldUpdater.java new file mode 100644 index 000000000..8a0ce0d66 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TReflectionBasedAtomicIntegerFieldUpdater.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import org.teavm.classlib.java.lang.TObject; +import org.teavm.classlib.java.lang.reflect.TField; + +class TReflectionBasedAtomicIntegerFieldUpdater extends TAtomicIntegerFieldUpdater { + private TField field; + + TReflectionBasedAtomicIntegerFieldUpdater(TField field) { + this.field = field; + } + + @Override + public boolean compareAndSet(T obj, int expect, int update) { + checkInstance(obj); + if (((Integer) field.getWithoutCheck(obj)) != expect) { + return false; + } + field.setWithoutCheck(obj, update); + return true; + } + + @Override + public boolean weakCompareAndSet(T obj, int expect, int update) { + return compareAndSet(obj, expect, update); + } + + @Override + public void set(T obj, int newValue) { + checkInstance(obj); + field.setWithoutCheck(obj, newValue); + } + + @Override + public void lazySet(T obj, int newValue) { + set(obj, newValue); + } + + @Override + public int get(T obj) { + checkInstance(obj); + return (Integer) field.getWithoutCheck(obj); + } + + private void checkInstance(T obj) { + if (!field.getDeclaringClass().isInstance((TObject) obj)) { + throw new ClassCastException(); + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TReflectionBasedAtomicLongFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TReflectionBasedAtomicLongFieldUpdater.java new file mode 100644 index 000000000..9b1a9aaf0 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TReflectionBasedAtomicLongFieldUpdater.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import org.teavm.classlib.java.lang.TObject; +import org.teavm.classlib.java.lang.reflect.TField; + +class TReflectionBasedAtomicLongFieldUpdater extends TAtomicLongFieldUpdater { + private TField field; + + TReflectionBasedAtomicLongFieldUpdater(TField field) { + this.field = field; + } + + @Override + public boolean compareAndSet(T obj, long expect, long update) { + checkInstance(obj); + if (((Long) field.getWithoutCheck(obj)) != expect) { + return false; + } + field.setWithoutCheck(obj, update); + return true; + } + + @Override + public boolean weakCompareAndSet(T obj, long expect, long update) { + return compareAndSet(obj, expect, update); + } + + @Override + public void set(T obj, long newValue) { + checkInstance(obj); + field.setWithoutCheck(obj, newValue); + } + + @Override + public void lazySet(T obj, long newValue) { + set(obj, newValue); + } + + @Override + public long get(T obj) { + checkInstance(obj); + return (Long) field.getWithoutCheck(obj); + } + + private void checkInstance(T obj) { + if (!field.getDeclaringClass().isInstance((TObject) obj)) { + throw new ClassCastException(); + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TReflectionBasedAtomicReferenceFieldUpdater.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TReflectionBasedAtomicReferenceFieldUpdater.java new file mode 100644 index 000000000..8bc1c25ca --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/atomic/TReflectionBasedAtomicReferenceFieldUpdater.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import org.teavm.classlib.java.lang.TObject; +import org.teavm.classlib.java.lang.reflect.TField; + +class TReflectionBasedAtomicReferenceFieldUpdater extends TAtomicReferenceFieldUpdater { + private TField field; + + TReflectionBasedAtomicReferenceFieldUpdater(TField field) { + this.field = field; + } + + @Override + public boolean compareAndSet(T obj, V expect, V update) { + checkInstance(obj); + if (field.getWithoutCheck(obj) != expect) { + return false; + } + field.setWithoutCheck(obj, update); + return true; + } + + @Override + public boolean weakCompareAndSet(T obj, V expect, V update) { + return compareAndSet(obj, expect, update); + } + + @Override + public void set(T obj, V newValue) { + checkInstance(obj); + field.setWithoutCheck(obj, newValue); + } + + @Override + public void lazySet(T obj, V newValue) { + set(obj, newValue); + } + + @SuppressWarnings("unchecked") + @Override + public V get(T obj) { + checkInstance(obj); + return (V) field.getWithoutCheck(obj); + } + + private void checkInstance(T obj) { + if (!field.getDeclaringClass().isInstance((TObject) obj)) { + throw new ClassCastException(); + } + } +} diff --git a/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java b/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java index 8ff7001ce..b2109fc5a 100644 --- a/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java +++ b/core/src/main/java/org/teavm/model/util/MissingItemsProcessor.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.teavm.dependency.DependencyInfo; @@ -27,8 +26,38 @@ import org.teavm.dependency.MethodDependencyInfo; import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.SupportedOn; import org.teavm.interop.UnsupportedOn; -import org.teavm.model.*; -import org.teavm.model.instructions.*; +import org.teavm.model.AnnotationContainerReader; +import org.teavm.model.AnnotationReader; +import org.teavm.model.AnnotationValue; +import org.teavm.model.BasicBlock; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassHierarchy; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassReader; +import org.teavm.model.FieldReference; +import org.teavm.model.Instruction; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.TextLocation; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.instructions.AbstractInstructionVisitor; +import org.teavm.model.instructions.CastInstruction; +import org.teavm.model.instructions.ClassConstantInstruction; +import org.teavm.model.instructions.ConstructArrayInstruction; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.ConstructMultiArrayInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InitClassInstruction; +import org.teavm.model.instructions.InstructionVisitor; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.IsInstanceInstruction; +import org.teavm.model.instructions.PutFieldInstruction; +import org.teavm.model.instructions.RaiseInstruction; +import org.teavm.model.instructions.StringConstantInstruction; import org.teavm.model.optimization.UnreachableBasicBlockEliminator; public class MissingItemsProcessor { @@ -98,45 +127,7 @@ public class MissingItemsProcessor { } private void truncateBlock(Instruction instruction) { - var transitionExtractor = new TransitionExtractor(); - var block = instruction.getBasicBlock(); - if (block.getLastInstruction() != null) { - block.getLastInstruction().acceptVisitor(transitionExtractor); - } - for (var successor : transitionExtractor.getTargets()) { - successor.removeIncomingsFrom(block); - } - - if (!block.getTryCatchBlocks().isEmpty()) { - var handlers = new LinkedHashSet(); - for (var tryCatch : block.getTryCatchBlocks()) { - handlers.add(tryCatch.getHandler()); - } - - var next = instruction; - var assignExtractor = new AssignmentExtractor(); - while (next != null) { - next.acceptVisitor(assignExtractor); - var definition = assignExtractor.getResult(); - if (definition != null) { - for (var handler : handlers) { - for (var phi : handler.getPhis()) { - for (var iter = phi.getIncomings().iterator(); iter.hasNext();) { - var incoming = iter.next(); - if (incoming.getSource() == block && incoming.getValue() == definition) { - iter.remove(); - } - } - } - } - } - next = next.getNext(); - } - } - - while (instruction.getNext() != null) { - instruction.getNext().delete(); - } + ProgramUtils.truncateBlock(instruction); instruction.insertNextAll(instructionsToAdd); instruction.delete(); } diff --git a/core/src/main/java/org/teavm/model/util/ProgramUtils.java b/core/src/main/java/org/teavm/model/util/ProgramUtils.java index 7f52dd982..a57b772bb 100644 --- a/core/src/main/java/org/teavm/model/util/ProgramUtils.java +++ b/core/src/main/java/org/teavm/model/util/ProgramUtils.java @@ -18,6 +18,7 @@ package org.teavm.model.util; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.teavm.ast.ControlFlowEntry; @@ -250,4 +251,46 @@ public final class ProgramUtils { } return varsDefinedInBlock; } + + public static void truncateBlock(Instruction instruction) { + var transitionExtractor = new TransitionExtractor(); + var block = instruction.getBasicBlock(); + if (block.getLastInstruction() != null) { + block.getLastInstruction().acceptVisitor(transitionExtractor); + } + for (var successor : transitionExtractor.getTargets()) { + successor.removeIncomingsFrom(block); + } + + if (!block.getTryCatchBlocks().isEmpty()) { + var handlers = new LinkedHashSet(); + for (var tryCatch : block.getTryCatchBlocks()) { + handlers.add(tryCatch.getHandler()); + } + + var next = instruction; + var assignExtractor = new AssignmentExtractor(); + while (next != null) { + next.acceptVisitor(assignExtractor); + var definition = assignExtractor.getResult(); + if (definition != null) { + for (var handler : handlers) { + for (var phi : handler.getPhis()) { + for (var iter = phi.getIncomings().iterator(); iter.hasNext();) { + var incoming = iter.next(); + if (incoming.getSource() == block && incoming.getValue() == definition) { + iter.remove(); + } + } + } + } + } + next = next.getNext(); + } + } + + while (instruction.getNext() != null) { + instruction.getNext().delete(); + } + } } diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java index b2e58bfc7..d5a983e68 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java @@ -24,6 +24,7 @@ import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Set; +import java.util.function.Supplier; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.SkipPlatform; @@ -121,6 +122,20 @@ public class ClassTest { assertEquals(1, ((TestObject) instance).getCounter()); } + @Test + public void instanceCreatedThoughReflectionWithConstantName() throws Exception { + var cls = Class.forName("org.teavm.classlib.java.lang.ClassTest$ClassReferredByConstantName"); + assertArrayEquals(new Class[] { Supplier.class }, cls.getInterfaces()); + assertEquals(Object.class, cls.getSuperclass()); + } + + private static class ClassReferredByConstantName implements Supplier { + @Override + public String get() { + return "constantNameWorks"; + } + } + @Test public void instanceCreatedThroughReflectionAsync() throws Exception { Runnable instance = TestObjectAsync.class.newInstance(); diff --git a/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicIntegerFieldUpdaterReflectionTest.java b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicIntegerFieldUpdaterReflectionTest.java new file mode 100644 index 000000000..fa01f55fa --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicIntegerFieldUpdaterReflectionTest.java @@ -0,0 +1,159 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.classlib.support.Reflectable; +import org.teavm.junit.SkipPlatform; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.TestPlatform; + +@RunWith(TeaVMTestRunner.class) +@SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI }) +public class AtomicIntegerFieldUpdaterReflectionTest { + private Class getInstanceType() { + return ClassWithField.class; + } + @Test + public void getSet() { + var updater = AtomicIntegerFieldUpdater.newUpdater(getInstanceType(), "value"); + var obj = new ClassWithField(); + obj.value = 1; + + assertEquals(1, updater.get(obj)); + updater.set(obj, 2); + assertEquals(2, obj.value); + + assertEquals(2, updater.getAndSet(obj, 3)); + assertEquals(3, obj.value); + + assertFalse(updater.compareAndSet(obj, 2, 4)); + assertEquals(3, obj.value); + assertTrue(updater.compareAndSet(obj, 3, 4)); + assertEquals(4, obj.value); + + assertEquals(4, updater.getAndIncrement(obj)); + assertEquals(5, obj.value); + + assertEquals(5, updater.getAndDecrement(obj)); + assertEquals(4, obj.value); + + assertEquals(4, updater.getAndAdd(obj, 2)); + assertEquals(6, obj.value); + + assertEquals(7, updater.incrementAndGet(obj)); + assertEquals(7, obj.value); + + assertEquals(6, updater.decrementAndGet(obj)); + assertEquals(6, obj.value); + + assertEquals(8, updater.addAndGet(obj, 2)); + assertEquals(8, obj.value); + + assertEquals(8, updater.getAndUpdate(obj, v -> v * 2)); + assertEquals(16, obj.value); + + assertEquals(8, updater.updateAndGet(obj, v -> v / 2)); + assertEquals(8, obj.value); + + assertEquals(8, updater.getAndAccumulate(obj, 3, (x, y) -> x * y)); + assertEquals(24, obj.value); + + assertEquals(48, updater.accumulateAndGet(obj, 2, (x, y) -> x * y)); + assertEquals(48, obj.value); + } + + @Test + public void nonVolatileField() { + try { + AtomicIntegerFieldUpdater.newUpdater(getInstanceType(), "nonVolatileValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void refField() { + try { + AtomicIntegerFieldUpdater.newUpdater(getInstanceType(), "refValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void staticField() { + try { + AtomicIntegerFieldUpdater.newUpdater(getInstanceType(), "staticValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void wrongFieldName() { + try { + AtomicIntegerFieldUpdater.newUpdater(getInstanceType(), "foo"); + fail("Expected exception not thrown"); + } catch (RuntimeException e) { + assertEquals(NoSuchFieldException.class, e.getCause().getClass()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void invalidClass() { + var updater = (AtomicIntegerFieldUpdater) AtomicIntegerFieldUpdater.newUpdater( + getInstanceType(), "value"); + var objUpdater = (AtomicIntegerFieldUpdater) updater; + try { + objUpdater.set(new Object(), 1); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + + try { + objUpdater.set(null, 2); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + } + + static class ClassWithField { + @Reflectable + volatile int value; + + @Reflectable + int nonVolatileValue; + + @Reflectable + volatile Object refValue; + + @Reflectable + static volatile int staticValue; + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicIntegerFieldUpdaterTest.java b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicIntegerFieldUpdaterTest.java new file mode 100644 index 000000000..df33fc01b --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicIntegerFieldUpdaterTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class AtomicIntegerFieldUpdaterTest { + @Test + public void getSet() { + var updater = AtomicIntegerFieldUpdater.newUpdater(ClassWithField.class, "value"); + var obj = new ClassWithField(); + obj.value = 1; + + assertEquals(1, updater.get(obj)); + updater.set(obj, 2); + assertEquals(2, obj.value); + + assertEquals(2, updater.getAndSet(obj, 3)); + assertEquals(3, obj.value); + + assertFalse(updater.compareAndSet(obj, 2, 4)); + assertEquals(3, obj.value); + assertTrue(updater.compareAndSet(obj, 3, 4)); + assertEquals(4, obj.value); + + assertEquals(4, updater.getAndIncrement(obj)); + assertEquals(5, obj.value); + + assertEquals(5, updater.getAndDecrement(obj)); + assertEquals(4, obj.value); + + assertEquals(4, updater.getAndAdd(obj, 2)); + assertEquals(6, obj.value); + + assertEquals(7, updater.incrementAndGet(obj)); + assertEquals(7, obj.value); + + assertEquals(6, updater.decrementAndGet(obj)); + assertEquals(6, obj.value); + + assertEquals(8, updater.addAndGet(obj, 2)); + assertEquals(8, obj.value); + + assertEquals(8, updater.getAndUpdate(obj, v -> v * 2)); + assertEquals(16, obj.value); + + assertEquals(8, updater.updateAndGet(obj, v -> v / 2)); + assertEquals(8, obj.value); + + assertEquals(8, updater.getAndAccumulate(obj, 3, (x, y) -> x * y)); + assertEquals(24, obj.value); + + assertEquals(48, updater.accumulateAndGet(obj, 2, (x, y) -> x * y)); + assertEquals(48, obj.value); + } + + @Test + public void nonVolatileField() { + try { + AtomicIntegerFieldUpdater.newUpdater(ClassWithField.class, "nonVolatileValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void refField() { + try { + AtomicIntegerFieldUpdater.newUpdater(ClassWithField.class, "refValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void staticField() { + try { + AtomicIntegerFieldUpdater.newUpdater(ClassWithField.class, "staticValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void wrongFieldName() { + try { + AtomicIntegerFieldUpdater.newUpdater(ClassWithField.class, "foo"); + fail("Expected exception not thrown"); + } catch (RuntimeException e) { + assertEquals(NoSuchFieldException.class, e.getCause().getClass()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void invalidClass() { + var updater = (AtomicIntegerFieldUpdater) AtomicIntegerFieldUpdater.newUpdater( + ClassWithField.class, "value"); + var objUpdater = (AtomicIntegerFieldUpdater) updater; + try { + objUpdater.set(new Object(), 1); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + + try { + objUpdater.set(null, 2); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + } + + static class ClassWithField { + volatile int value; + int nonVolatileValue; + volatile Object refValue; + static volatile int staticValue; + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicLongFieldUpdaterReflectionTest.java b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicLongFieldUpdaterReflectionTest.java new file mode 100644 index 000000000..cc0260e7a --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicLongFieldUpdaterReflectionTest.java @@ -0,0 +1,159 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.classlib.support.Reflectable; +import org.teavm.junit.SkipPlatform; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.TestPlatform; + +@RunWith(TeaVMTestRunner.class) +@SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI }) +public class AtomicLongFieldUpdaterReflectionTest { + private Class getInstanceType() { + return ClassWithField.class; + } + + @Test + public void getSet() { + var updater = AtomicLongFieldUpdater.newUpdater(getInstanceType(), "value"); + var obj = new ClassWithField(); + obj.value = 1; + + assertEquals(1, updater.get(obj)); + updater.set(obj, 2); + assertEquals(2, obj.value); + + assertEquals(2, updater.getAndSet(obj, 3)); + assertEquals(3, obj.value); + + assertFalse(updater.compareAndSet(obj, 2, 4)); + assertEquals(3, obj.value); + assertTrue(updater.compareAndSet(obj, 3, 4)); + assertEquals(4, obj.value); + + assertEquals(4, updater.getAndIncrement(obj)); + assertEquals(5, obj.value); + + assertEquals(5, updater.getAndDecrement(obj)); + assertEquals(4, obj.value); + + assertEquals(4, updater.getAndAdd(obj, 2)); + assertEquals(6, obj.value); + + assertEquals(7, updater.incrementAndGet(obj)); + assertEquals(7, obj.value); + + assertEquals(6, updater.decrementAndGet(obj)); + assertEquals(6, obj.value); + + assertEquals(8, updater.addAndGet(obj, 2)); + assertEquals(8, obj.value); + + assertEquals(8, updater.getAndUpdate(obj, v -> v * 2)); + assertEquals(16, obj.value); + + assertEquals(8, updater.updateAndGet(obj, v -> v / 2)); + assertEquals(8, obj.value); + + assertEquals(8, updater.getAndAccumulate(obj, 3, (x, y) -> x * y)); + assertEquals(24, obj.value); + + assertEquals(48, updater.accumulateAndGet(obj, 2, (x, y) -> x * y)); + assertEquals(48, obj.value); + } + + @Test + public void nonVolatileField() { + try { + AtomicLongFieldUpdater.newUpdater(getInstanceType(), "nonVolatileValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void refField() { + try { + AtomicLongFieldUpdater.newUpdater(getInstanceType(), "refValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void staticField() { + try { + AtomicLongFieldUpdater.newUpdater(getInstanceType(), "staticValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void wrongFieldName() { + try { + AtomicLongFieldUpdater.newUpdater(getInstanceType(), "foo"); + fail("Expected exception not thrown"); + } catch (RuntimeException e) { + assertEquals(NoSuchFieldException.class, e.getCause().getClass()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void invalidClass() { + var updater = (AtomicLongFieldUpdater) AtomicLongFieldUpdater.newUpdater(getInstanceType(), "value"); + var objUpdater = (AtomicLongFieldUpdater) updater; + try { + objUpdater.set(new Object(), 1); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + + try { + objUpdater.set(null, 2); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + } + + static class ClassWithField { + @Reflectable + volatile long value; + + @Reflectable + long nonVolatileValue; + + @Reflectable + volatile Object refValue; + + @Reflectable + static volatile long staticValue; + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicLongFieldUpdaterTest.java b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicLongFieldUpdaterTest.java new file mode 100644 index 000000000..c527457b7 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicLongFieldUpdaterTest.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class AtomicLongFieldUpdaterTest { + @Test + public void getSet() { + var updater = AtomicLongFieldUpdater.newUpdater(ClassWithField.class, "value"); + var obj = new ClassWithField(); + obj.value = 1; + + assertEquals(1, updater.get(obj)); + updater.set(obj, 2); + assertEquals(2, obj.value); + + assertEquals(2, updater.getAndSet(obj, 3)); + assertEquals(3, obj.value); + + assertFalse(updater.compareAndSet(obj, 2, 4)); + assertEquals(3, obj.value); + assertTrue(updater.compareAndSet(obj, 3, 4)); + assertEquals(4, obj.value); + + assertEquals(4, updater.getAndIncrement(obj)); + assertEquals(5, obj.value); + + assertEquals(5, updater.getAndDecrement(obj)); + assertEquals(4, obj.value); + + assertEquals(4, updater.getAndAdd(obj, 2)); + assertEquals(6, obj.value); + + assertEquals(7, updater.incrementAndGet(obj)); + assertEquals(7, obj.value); + + assertEquals(6, updater.decrementAndGet(obj)); + assertEquals(6, obj.value); + + assertEquals(8, updater.addAndGet(obj, 2)); + assertEquals(8, obj.value); + + assertEquals(8, updater.getAndUpdate(obj, v -> v * 2)); + assertEquals(16, obj.value); + + assertEquals(8, updater.updateAndGet(obj, v -> v / 2)); + assertEquals(8, obj.value); + + assertEquals(8, updater.getAndAccumulate(obj, 3, (x, y) -> x * y)); + assertEquals(24, obj.value); + + assertEquals(48, updater.accumulateAndGet(obj, 2, (x, y) -> x * y)); + assertEquals(48, obj.value); + } + + @Test + public void nonVolatileField() { + try { + AtomicLongFieldUpdater.newUpdater(ClassWithField.class, "nonVolatileValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void refField() { + try { + AtomicLongFieldUpdater.newUpdater(ClassWithField.class, "refValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void staticField() { + try { + AtomicLongFieldUpdater.newUpdater(ClassWithField.class, "staticValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void wrongFieldName() { + try { + AtomicLongFieldUpdater.newUpdater(ClassWithField.class, "foo"); + fail("Expected exception not thrown"); + } catch (RuntimeException e) { + assertEquals(NoSuchFieldException.class, e.getCause().getClass()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void invalidClass() { + var updater = (AtomicLongFieldUpdater) AtomicLongFieldUpdater.newUpdater(ClassWithField.class, "value"); + var objUpdater = (AtomicLongFieldUpdater) updater; + try { + objUpdater.set(new Object(), 1); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + + try { + objUpdater.set(null, 2); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + } + + static class ClassWithField { + volatile long value; + long nonVolatileValue; + volatile Object refValue; + static volatile long staticValue; + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicReferenceFieldUpdaterReflectionTest.java b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicReferenceFieldUpdaterReflectionTest.java new file mode 100644 index 000000000..afb4f4900 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicReferenceFieldUpdaterReflectionTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.classlib.support.Reflectable; +import org.teavm.junit.SkipPlatform; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.TestPlatform; + +@RunWith(TeaVMTestRunner.class) +@SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI }) +public class AtomicReferenceFieldUpdaterReflectionTest { + private Class getInstanceType() { + return ClassWithField.class; + } + + @Test + public void getSet() { + var updater = AtomicReferenceFieldUpdater.newUpdater(getInstanceType(), ValueClass.class, "value"); + var obj = new ClassWithField(); + var a = new ValueClass("a"); + var b = new ValueClass("b"); + var c = new ValueClass("c"); + obj.value = a; + + assertSame(a, updater.get(obj)); + updater.set(obj, b); + assertSame(b, obj.value); + + assertSame(b, updater.getAndSet(obj, a)); + assertSame(a, obj.value); + + assertFalse(updater.compareAndSet(obj, b, c)); + assertSame(a, obj.value); + assertTrue(updater.compareAndSet(obj, a, c)); + assertSame(c, obj.value); + + assertSame(c, updater.getAndUpdate(obj, v -> new ValueClass(v.v + "1"))); + assertEquals("c1", obj.value.v); + + assertEquals("c11", updater.updateAndGet(obj, v -> new ValueClass(v.v + "1")).v); + assertEquals("c11", obj.value.v); + + assertEquals("c11", updater.getAndAccumulate(obj, b, (x, y) -> new ValueClass(x.v + "," + y.v)).v); + assertEquals("c11,b", obj.value.v); + + assertEquals("c11,b,a", updater.accumulateAndGet(obj, a, (x, y) -> new ValueClass(x.v + "," + y.v)).v); + assertEquals("c11,b,a", obj.value.v); + } + + @Test + public void wrongType() { + try { + AtomicReferenceFieldUpdater.newUpdater(getInstanceType(), String.class, "value"); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + } + + @Test + public void nonVolatileField() { + try { + AtomicReferenceFieldUpdater.newUpdater(getInstanceType(), ValueClass.class, "nonVolatileValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void primitiveField() { + try { + AtomicReferenceFieldUpdater.newUpdater(getInstanceType(), int.class, "intValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void staticField() { + try { + AtomicReferenceFieldUpdater.newUpdater(getInstanceType(), ValueClass.class, "staticValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void wrongFieldName() { + try { + AtomicReferenceFieldUpdater.newUpdater(getInstanceType(), ValueClass.class, "foo"); + fail("Expected exception not thrown"); + } catch (RuntimeException e) { + assertEquals(NoSuchFieldException.class, e.getCause().getClass()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void invalidClass() { + var updater = (AtomicReferenceFieldUpdater) AtomicReferenceFieldUpdater.newUpdater( + getInstanceType(), ValueClass.class, "value"); + var objUpdater = (AtomicReferenceFieldUpdater) updater; + try { + objUpdater.set(new Object(), new ValueClass("")); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + + try { + objUpdater.set(null, new ValueClass("")); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + } + + static class ClassWithField { + @Reflectable + volatile ValueClass value; + + @Reflectable + ValueClass nonVolatileValue; + + @Reflectable + volatile int intValue; + + @Reflectable + static volatile ValueClass staticValue; + } + + private static class ValueClass { + String v; + + ValueClass(String v) { + this.v = v; + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicReferenceFieldUpdaterTest.java b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicReferenceFieldUpdaterTest.java new file mode 100644 index 000000000..f82e1bce9 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/AtomicReferenceFieldUpdaterTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class AtomicReferenceFieldUpdaterTest { + @Test + public void getSet() { + var updater = AtomicReferenceFieldUpdater.newUpdater(ClassWithField.class, ValueClass.class, "value"); + var obj = new ClassWithField(); + var a = new ValueClass("a"); + var b = new ValueClass("b"); + var c = new ValueClass("c"); + obj.value = a; + + assertSame(a, updater.get(obj)); + updater.set(obj, b); + assertSame(b, obj.value); + + assertSame(b, updater.getAndSet(obj, a)); + assertSame(a, obj.value); + + assertFalse(updater.compareAndSet(obj, b, c)); + assertSame(a, obj.value); + assertTrue(updater.compareAndSet(obj, a, c)); + assertSame(c, obj.value); + + assertSame(c, updater.getAndUpdate(obj, v -> new ValueClass(v.v + "1"))); + assertEquals("c1", obj.value.v); + + assertEquals("c11", updater.updateAndGet(obj, v -> new ValueClass(v.v + "1")).v); + assertEquals("c11", obj.value.v); + + assertEquals("c11", updater.getAndAccumulate(obj, b, (x, y) -> new ValueClass(x.v + "," + y.v)).v); + assertEquals("c11,b", obj.value.v); + + assertEquals("c11,b,a", updater.accumulateAndGet(obj, a, (x, y) -> new ValueClass(x.v + "," + y.v)).v); + assertEquals("c11,b,a", obj.value.v); + } + + @Test + public void wrongType() { + try { + AtomicReferenceFieldUpdater.newUpdater(ClassWithField.class, String.class, "value"); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + } + + @Test + public void nonVolatileField() { + try { + AtomicReferenceFieldUpdater.newUpdater(ClassWithField.class, ValueClass.class, "nonVolatileValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void primitiveField() { + try { + AtomicReferenceFieldUpdater.newUpdater(ClassWithField.class, int.class, "intValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void staticField() { + try { + AtomicReferenceFieldUpdater.newUpdater(ClassWithField.class, ValueClass.class, "staticValue"); + fail("Expected exception not thrown"); + } catch (IllegalArgumentException e) { + // ok + } + } + + @Test + public void wrongFieldName() { + try { + AtomicReferenceFieldUpdater.newUpdater(ClassWithField.class, ValueClass.class, "foo"); + fail("Expected exception not thrown"); + } catch (RuntimeException e) { + assertEquals(NoSuchFieldException.class, e.getCause().getClass()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void invalidClass() { + var updater = (AtomicReferenceFieldUpdater) AtomicReferenceFieldUpdater.newUpdater( + ClassWithField.class, ValueClass.class, "value"); + var objUpdater = (AtomicReferenceFieldUpdater) updater; + try { + objUpdater.set(new Object(), new ValueClass("")); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + + try { + objUpdater.set(null, new ValueClass("")); + fail("Expected exception not thrown"); + } catch (ClassCastException e) { + // ok + } + } + + static class ClassWithField { + volatile ValueClass value; + ValueClass nonVolatileValue; + volatile int intValue; + static volatile ValueClass staticValue; + } + + private static class ValueClass { + String v; + + ValueClass(String v) { + this.v = v; + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/MultiThreadedFieldUpdaterTest.java b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/MultiThreadedFieldUpdaterTest.java new file mode 100644 index 000000000..301f288d0 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/atomic/MultiThreadedFieldUpdaterTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util.concurrent.atomic; + +import static org.junit.Assert.assertEquals; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.classlib.support.Reflectable; +import org.teavm.junit.SkipPlatform; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.TestPlatform; + +@RunWith(TeaVMTestRunner.class) +@SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI }) +public class MultiThreadedFieldUpdaterTest { + @Test + public void getAndUpdate() { + var updater = AtomicReferenceFieldUpdater.newUpdater(TestClass.class, String.class, "value"); + var obj = new TestClass(); + obj.value = "foo"; + + var monitor1 = new Monitor(); + var monitor2 = new Monitor(); + var thread = new Thread(() -> { + synchronized (monitor1) { + if (monitor1.count > 0) { + try { + monitor1.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + updater.set(obj, "bar"); + synchronized (monitor2) { + monitor2.count = 0; + monitor2.notifyAll(); + } + }); + thread.setDaemon(true); + thread.start(); + + updater.getAndUpdate(obj, value -> { + synchronized (monitor1) { + monitor1.count = 0; + monitor1.notifyAll(); + } + var result = value + "!"; + synchronized (monitor2) { + if (monitor2.count > 0) { + try { + monitor2.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + return result; + }); + + assertEquals("bar!", obj.value); + } + + private static class TestClass { + @Reflectable + volatile String value; + } + + private static class Monitor { + int count = 1; + } +}