classlib: implement Atomic*FieldUpdater classes

Fix #750
This commit is contained in:
Alexey Andreev 2023-10-14 22:29:34 +02:00
parent 2513ceb236
commit 5bc398415d
27 changed files with 2410 additions and 250 deletions

View File

@ -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;
}
}

View File

@ -28,6 +28,7 @@ import org.teavm.classlib.impl.currency.CurrenciesGenerator;
import org.teavm.classlib.impl.currency.CurrencyHelper; import org.teavm.classlib.impl.currency.CurrencyHelper;
import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor; import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor;
import org.teavm.classlib.impl.record.ObjectMethodsSubstitutor; 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.DateTimeZoneProvider;
import org.teavm.classlib.impl.tz.DateTimeZoneProviderIntrinsic; import org.teavm.classlib.impl.tz.DateTimeZoneProviderIntrinsic;
import org.teavm.classlib.impl.tz.DateTimeZoneProviderPatch; import org.teavm.classlib.impl.tz.DateTimeZoneProviderPatch;
@ -94,7 +95,7 @@ public class JCLPlugin implements TeaVMPlugin {
if (!isBootstrap()) { if (!isBootstrap()) {
host.registerService(CLDRReader.class, CLDRReader.getInstance(host.getProperties(), host.getClassLoader())); host.registerService(CLDRReader.class, CLDRReader.getInstance(host.getProperties(), host.getClassLoader()));
host.add(new ClassForNameTransformer()); host.add(new ReflectionTransformer());
} }
host.add(new AnnotationDependencyListener()); host.add(new AnnotationDependencyListener());

View File

@ -43,8 +43,9 @@ import org.teavm.model.ValueType;
public class ReflectionDependencyListener extends AbstractDependencyListener { public class ReflectionDependencyListener extends AbstractDependencyListener {
private List<ReflectionSupplier> reflectionSuppliers; private List<ReflectionSupplier> reflectionSuppliers;
private MethodReference fieldGet = new MethodReference(Field.class, "get", Object.class, Object.class); private MethodReference fieldGet = new MethodReference(Field.class, "getWithoutCheck", Object.class, Object.class);
private MethodReference fieldSet = new MethodReference(Field.class, "set", Object.class, Object.class, void.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, private MethodReference newInstance = new MethodReference(Constructor.class, "newInstance", Object[].class,
Object.class); Object.class);
private MethodReference invokeMethod = new MethodReference(Method.class, "invoke", Object.class, Object[].class, private MethodReference invokeMethod = new MethodReference(Method.class, "invoke", Object.class, Object[].class,

View File

@ -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<String, String> 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("<init>", ValueType.VOID);
ctor.setLevel(AccessLevel.PRIVATE);
var pe = ProgramEmitter.create(ctor, hierarchy);
pe.var(0, AtomicReferenceFieldUpdater.class).invokeSpecial(cls.getParent(), "<init>", ValueType.VOID);
pe.exit();
return ctor;
}
private MethodHolder createInitializer(ClassHolder cls, ClassHierarchy hierarchy) {
var initializer = new MethodHolder("<clinit>", 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, "<init>", void.class));
wrappedInit.setLocation(instruction.getLocation());
block.add(wrappedInit);
init.setMethod(new MethodReference(exceptionType, "<init>", Throwable.class, void.class));
init.setArguments(wrappedConstruct.getReceiver());
} else {
init.setMethod(new MethodReference(exceptionType, "<init>", 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;
}
}

View File

@ -354,19 +354,19 @@ public class TClass<T> extends TObject implements TAnnotatedElement, TType {
return fields.clone(); return fields.clone();
} }
public TField getDeclaredField(String name) throws TNoSuchFieldError { public TField getDeclaredField(String name) throws TNoSuchFieldException {
for (TField field : getDeclaredFields()) { for (TField field : getDeclaredFields()) {
if (field.getName().equals(name)) { if (field.getName().equals(name)) {
return field; 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<>()); TField result = findField(name, new HashSet<>());
if (result == null) { if (result == null) {
throw new TNoSuchFieldError(); throw new TNoSuchFieldException();
} }
return result; return result;
} }

View File

@ -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);
}
}

View File

@ -86,19 +86,23 @@ public class TField extends TAccessibleObject implements TMember {
} }
public Object get(Object obj) throws TIllegalArgumentException, TIllegalAccessException { public Object get(Object obj) throws TIllegalArgumentException, TIllegalAccessException {
if (getter == null) { checkGetAccess();
throw new TIllegalAccessException();
}
checkInstance(obj); checkInstance(obj);
return getWithoutCheck(obj);
}
public Object getWithoutCheck(Object obj) {
PlatformObject result = getter.get(Platform.getPlatformObject(obj)); PlatformObject result = getter.get(Platform.getPlatformObject(obj));
return Converter.toJava(result); return Converter.toJava(result);
} }
public void set(Object obj, Object value) throws TIllegalArgumentException, TIllegalAccessException { public void set(Object obj, Object value) throws TIllegalArgumentException, TIllegalAccessException {
if (setter == null) { checkSetAccess();
throw new TIllegalAccessException();
}
checkInstance(obj); checkInstance(obj);
setWithoutCheck(obj, value);
}
public void setWithoutCheck(Object obj, Object value) {
setter.set(Platform.getPlatformObject(obj), Converter.fromJava(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();
}
}
} }

View File

@ -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<T> {
protected TAtomicIntegerFieldUpdater() {
}
@SuppressWarnings("EqualsBetweenInconvertibleTypes")
public static <U> TAtomicIntegerFieldUpdater<U> newUpdater(TClass<U> 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;
}
}
}
}

View File

@ -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<T> {
protected TAtomicLongFieldUpdater() {
}
@SuppressWarnings("EqualsBetweenInconvertibleTypes")
public static <U> TAtomicLongFieldUpdater<U> newUpdater(TClass<U> 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;
}
}
}
}

View File

@ -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<T, V> {
protected TAtomicReferenceFieldUpdater() {
}
public static <U, W> TAtomicReferenceFieldUpdater<U, W> newUpdater(TClass<U> tclass, TClass<W> 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<V> 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<V> 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<V> 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<V> accumulatorFunction) {
while (true) {
var currentValue = get(obj);
var newValue = accumulatorFunction.apply(currentValue, x);
if (compareAndSet(obj, currentValue, newValue)) {
return newValue;
}
}
}
}

View File

@ -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<T> extends TAtomicIntegerFieldUpdater<T> {
@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);
}
}

View File

@ -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<T> extends TAtomicLongFieldUpdater<T> {
@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);
}
}

View File

@ -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<T, V> extends TAtomicReferenceFieldUpdater<T, V> {
@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);
}
}

View File

@ -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<T>
extends TBaseAtomicReferenceFieldUpdater<T, Long> {
@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);
}

View File

@ -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<T> extends TAtomicIntegerFieldUpdater<T> {
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();
}
}
}

View File

@ -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<T> extends TAtomicLongFieldUpdater<T> {
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();
}
}
}

View File

@ -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<T, V> extends TAtomicReferenceFieldUpdater<T, V> {
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();
}
}
}

View File

@ -19,7 +19,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyInfo;
@ -27,8 +26,38 @@ import org.teavm.dependency.MethodDependencyInfo;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.interop.SupportedOn; import org.teavm.interop.SupportedOn;
import org.teavm.interop.UnsupportedOn; import org.teavm.interop.UnsupportedOn;
import org.teavm.model.*; import org.teavm.model.AnnotationContainerReader;
import org.teavm.model.instructions.*; 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; import org.teavm.model.optimization.UnreachableBasicBlockEliminator;
public class MissingItemsProcessor { public class MissingItemsProcessor {
@ -98,45 +127,7 @@ public class MissingItemsProcessor {
} }
private void truncateBlock(Instruction instruction) { private void truncateBlock(Instruction instruction) {
var transitionExtractor = new TransitionExtractor(); ProgramUtils.truncateBlock(instruction);
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<BasicBlock>();
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();
}
instruction.insertNextAll(instructionsToAdd); instruction.insertNextAll(instructionsToAdd);
instruction.delete(); instruction.delete();
} }

View File

@ -18,6 +18,7 @@ package org.teavm.model.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.teavm.ast.ControlFlowEntry; import org.teavm.ast.ControlFlowEntry;
@ -250,4 +251,46 @@ public final class ProgramUtils {
} }
return varsDefinedInBlock; 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<BasicBlock>();
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();
}
}
} }

View File

@ -24,6 +24,7 @@ import java.lang.annotation.Annotation;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.teavm.junit.SkipPlatform; import org.teavm.junit.SkipPlatform;
@ -121,6 +122,20 @@ public class ClassTest {
assertEquals(1, ((TestObject) instance).getCounter()); 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<String> {
@Override
public String get() {
return "constantNameWorks";
}
}
@Test @Test
public void instanceCreatedThroughReflectionAsync() throws Exception { public void instanceCreatedThroughReflectionAsync() throws Exception {
Runnable instance = TestObjectAsync.class.newInstance(); Runnable instance = TestObjectAsync.class.newInstance();

View File

@ -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<ClassWithField> 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<Object>) 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;
}
}

View File

@ -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<Object>) 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;
}
}

View File

@ -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<ClassWithField> 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<Object>) 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;
}
}

View File

@ -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<Object>) 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;
}
}

View File

@ -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<ClassWithField> 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<Object, ValueClass>) 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;
}
}
}

View File

@ -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<Object, ValueClass>) 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;
}
}
}

View File

@ -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;
}
}