org.aion.avm.core.NodeEnvironment Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javaee-rt Show documentation
Show all versions of javaee-rt Show documentation
An Execution Environment for Java SCOREs
package org.aion.avm.core;
import i.ConstantsHolder;
import i.FrameContext;
import i.IInstrumentation;
import i.InstrumentationHelpers;
import i.InternedClasses;
import i.OutOfEnergyException;
import i.PackageConstants;
import i.RuntimeAssertionError;
import org.aion.avm.core.classgeneration.CommonGenerators;
import org.aion.avm.core.classloading.AvmClassLoader;
import org.aion.avm.core.classloading.AvmSharedClassLoader;
import org.aion.avm.core.dappreading.LoadedJar;
import org.aion.avm.core.instrument.JCLAndAPIHeapInstanceSize;
import org.aion.avm.core.types.ClassHierarchy;
import org.aion.avm.core.types.ClassHierarchyBuilder;
import org.aion.avm.core.types.ClassInformation;
import org.aion.avm.core.types.ClassInformationFactory;
import org.aion.avm.core.util.MethodDescriptorCollector;
import org.aion.avm.utilities.Utilities;
import p.score.Address;
import p.score.Context;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Represents the long-lived global state of a specific "node" instance.
* For now, this just contains the AvmSharedClassLoader (since it is stateless and shared by all transactions run on this
* NodeEnvironment - that is, each AvmImpl instance).
* Note that this is also responsible for any bootstrap initialization of the shared environment. Specifically, this involves
* eagerly loading the shadow JDK in order to run their methods.
*/
public class NodeEnvironment {
// NOTE: This is only temporarily a singleton and will probably see its relationship inverted, in the future: becoming the Avm factory.
public static final NodeEnvironment singleton = new NodeEnvironment();
private final AvmSharedClassLoader sharedClassLoader;
// Note that the constant map is a map of constant hashcodes to constant instances. This is just provided so that reference deserialization
// mechanisms can map from this primitive identity into the actual instances.
private final Map constantMap;
private final Class>[] shadowApiClasses;
// contains all the shadow classes except the exception classes that are generated automatically; used for computing runtime object sizes
private final Class>[] shadowClasses;
// contains all the supported jcl class names (slash type)
private final Set jclClassNames;
public final Map preRenameRuntimeObjectSizeMap; // pre-rename; runtime objects including shadow objects, exceptions and API objects
public final Map postRenameRuntimeObjectSizeMap; // post-rename; runtime objects including shadow objects, exceptions and API objects
public final Map> shadowClassSlashNameMethodDescriptorMap;
// The full class hierarchy; we only ever give away deep copies of this object!
private final ClassHierarchy classHierarchy;
private NodeEnvironment() {
Map generatedShadowJDK = CommonGenerators.generateShadowJDK();
this.sharedClassLoader = new AvmSharedClassLoader(generatedShadowJDK);
try {
this.shadowApiClasses = new Class>[] {
Address.class,
Context.class,
};
Class>[] arrayWrapperClasses = new Class>[]{
a.IArray.class
, a.Array.class
, a.ArrayElement.class
, a.BooleanArray.class
, a.ByteArray.class
, a.CharArray.class
, a.DoubleArray.class
, a.FloatArray.class
, a.IntArray.class
, a.LongArray.class
, a.ObjectArray.class
, a.ShortArray.class
};
Class>[] exceptionWrapperClasses = new Class>[]{
e.s.java.lang.Throwable.class
};
this.shadowClasses = new Class>[] {
s.java.lang.AssertionError.class
, s.java.lang.Boolean.class
, s.java.lang.Byte.class
, s.java.lang.Character.class
, s.java.lang.CharSequence.class
, s.java.lang.Class.class
, s.java.lang.Comparable.class
, s.java.lang.Double.class
, s.java.lang.Enum.class
, s.java.lang.EnumConstantNotPresentException.class
, s.java.lang.Error.class
, s.java.lang.Exception.class
, s.java.lang.Float.class
, s.java.lang.Integer.class
, s.java.lang.Iterable.class
, s.java.lang.Long.class
, s.java.lang.Math.class
, s.java.lang.Number.class
, s.java.lang.Object.class
, s.java.lang.Runnable.class
, s.java.lang.RuntimeException.class
, s.java.lang.Short.class
, s.java.lang.StrictMath.class
, s.java.lang.String.class
, s.java.lang.StringBuffer.class
, s.java.lang.StringBuilder.class
, s.java.lang.System.class
, s.java.lang.Throwable.class
, s.java.lang.TypeNotPresentException.class
, s.java.lang.Appendable.class
, s.java.lang.Cloneable.class
, s.java.lang.invoke.LambdaMetafactory.class
, s.java.lang.invoke.StringConcatFactory.class
, s.java.lang.Void.class
, s.java.math.BigDecimal.class
, s.java.math.BigInteger.class
, s.java.math.MathContext.class
, s.java.math.RoundingMode.class
, s.java.util.Arrays.class
, s.java.util.Collection.class
, s.java.util.Iterator.class
, s.java.util.ListIterator.class
, s.java.util.Map.class
, s.java.util.Map.Entry.class
, s.java.util.NoSuchElementException.class
, s.java.util.Set.class
, s.java.util.List.class
, s.java.util.function.Function.class
, s.java.util.concurrent.TimeUnit.class
, s.java.io.Serializable.class
, s.score.RevertedException.class
, s.score.UserRevertedException.class
, s.score.UserRevertException.class
};
this.jclClassNames = new HashSet<>();
// include the shadow classes we implement
this.jclClassNames.addAll(loadShadowClasses(NodeEnvironment.class.getClassLoader(), shadowClasses));
// we have to add the common generated exception/error classes as it's not pre-loaded
this.jclClassNames.addAll(Stream.of(CommonGenerators.kExceptionClassNames)
.map(Utilities::fullyQualifiedNameToInternalName)
.collect(Collectors.toList()));
// include the invoke classes
this.jclClassNames.add("java/lang/invoke/MethodHandles");
this.jclClassNames.add("java/lang/invoke/MethodHandle");
this.jclClassNames.add("java/lang/invoke/MethodType");
this.jclClassNames.add("java/lang/invoke/CallSite");
this.jclClassNames.add("java/lang/invoke/MethodHandles$Lookup");
// Finish the initialization of shared class loader
// Inject pre generated wrapper class into shared classloader enable more optimization opportunities for us
this.sharedClassLoader.putIntoDynamicCache(arrayWrapperClasses);
// Inject shadow and api class into shared classloader so we can build a static cache
this.sharedClassLoader.putIntoStaticCache(this.shadowClasses);
this.sharedClassLoader.putIntoStaticCache(this.shadowApiClasses);
this.sharedClassLoader.putIntoStaticCache(exceptionWrapperClasses);
this.sharedClassLoader.finishInitialization();
} catch (ClassNotFoundException e) {
// This would be a fatal startup error.
throw RuntimeAssertionError.unexpected(e);
}
// Create the constant map.
this.constantMap = Collections.unmodifiableMap(ConstantsHolder.getConstants());
RuntimeAssertionError.assertTrue(this.constantMap.size() == 34);
// create the object size look-up maps
Map rtObjectSizeMap = computeRuntimeObjectSizes();
// This is to ensure the JCLAndAPIHeapInstanceSize is updated with the correct instance size of a newly added JCL or API class
RuntimeAssertionError.assertTrue(rtObjectSizeMap.size() == 98);
Map shadowObjectSizeMap = new HashMap<>(); // pre-rename; shadow objects and exceptions
Map apiObjectSizeMap = new HashMap<>(); // post-rename; API objects
Map preRenameObjectSizes = new HashMap<>();
Map postRenameObjectSizes = new HashMap<>();
rtObjectSizeMap.forEach((k, v) -> {
// the shadowed object sizes; and change the class name to the non-shadowed version
if (k.startsWith(PackageConstants.kShadowSlashPrefix)) {
shadowObjectSizeMap.put(k.substring(PackageConstants.kShadowSlashPrefix.length()), v);
postRenameObjectSizes.put(k, v);
}
// the object size of API classes
if (k.startsWith(PackageConstants.kShadowApiSlashPrefix)) {
apiObjectSizeMap.put(k, v);
preRenameObjectSizes.put(k.substring(PackageConstants.kShadowApiSlashPrefix.length()), v);
}
});
preRenameObjectSizes.putAll(shadowObjectSizeMap);
postRenameObjectSizes.putAll(apiObjectSizeMap);
this.preRenameRuntimeObjectSizeMap = Collections.unmodifiableMap(preRenameObjectSizes);
this.postRenameRuntimeObjectSizeMap = Collections.unmodifiableMap(postRenameObjectSizes);
this.shadowClassSlashNameMethodDescriptorMap = Collections.unmodifiableMap(getShadowClassSlashNameMethodDescriptorMap());
this.classHierarchy = buildJCLAndAPIClassHierarchy();
}
public static NodeEnvironment getInstance() {
return singleton;
}
// This is an example of the more "factory-like" nature of the NodeEnvironment.
public AvmClassLoader createInvocationClassLoader(Map finalContractClasses) {
return new AvmClassLoader(this.sharedClassLoader, finalContractClasses);
}
public Class> loadSharedClass(String name) throws ClassNotFoundException {
return Class.forName(name, true, this.sharedClassLoader);
}
/**
* This method only exists for unit tests. Returns true if clazz was loaded by the shared loader.
*/
public boolean isClassFromSharedLoader(Class> clazz) {
return (this.sharedClassLoader == clazz.getClassLoader());
}
/**
* Returns whether the class is from our custom JCL.
*/
public boolean isClassFromJCL(String classNameSlash) {
return this.jclClassNames.contains(classNameSlash);
}
public List getJclSlashClassNames() {
return new ArrayList<>(this.jclClassNames);
}
/**
* @return The map of constants (specified constant identity hash codes to constant instances).
*/
public Map getConstantMap() {
return this.constantMap;
}
private static Set loadShadowClasses(ClassLoader loader, Class>[] shadowClasses) throws ClassNotFoundException {
// Create the fake IInstrumentation.
IInstrumentation instrumentation = new IInstrumentation() {
@Override
public void chargeEnergy(long cost) throws OutOfEnergyException {
}
@Override
public void chargeEnergy(int cost) throws OutOfEnergyException {
// Shadow enum class will create array wrapper with
// Ignore the charge energy request in this case
}
@Override
public boolean tryChargeEnergy(int cost) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public long energyLeft() {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public s.java.lang.Class wrapAsClass(Class input) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public int getNextHashCodeAndIncrement() {
// Only constants should end up being allocated under this so set them to the constant hash code we will over-write with their
// specification values, after.
return Integer.MIN_VALUE;
}
@Override
public void bootstrapOnly() {
// This is ok since we are the bootstrapping helper.
}
@Override
public s.java.lang.String wrapAsString(String input) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public s.java.lang.Object unwrapThrowable(Throwable t) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public Throwable wrapAsThrowable(s.java.lang.Object arg) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public int getCurStackSize() {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public int getCurStackDepth() {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public void enterMethod(int frameSize) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public void exitMethod(int frameSize) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public void enterCatchBlock(int depth, int size) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public int peekNextHashCode() {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public void forceNextHashCode(int nextHashCode) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public void enterNewFrame(ClassLoader contractLoader, long energyLeft, int nextHashCode, InternedClasses classWrappers, FrameContext frameContext) {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public void exitCurrentFrame() {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
@Override
public boolean isLoadedByCurrentClassLoader(java.lang.Class> userClass) {
throw RuntimeAssertionError.unreachable("Not expected here.");
}
@Override
public FrameContext getFrameContext() {
throw RuntimeAssertionError.unreachable("Nobody should be calling this");
}
};
// Load all the classes - even just mentioning these might cause them to be loaded, even before the Class.forName().
InstrumentationHelpers.attachThread(instrumentation);
Set loadedClassNames = loadAndInitializeClasses(loader, shadowClasses);
// TODO: refactor this
// load and initialize api impl class with static field
loadAndInitializeClasses(loader,
pi.UnmodifiableArrayMap.class,
pi.UnmodifiableArrayList.class
);
InstrumentationHelpers.detachThread(instrumentation);
return loadedClassNames;
}
private static Set loadAndInitializeClasses(ClassLoader loader, Class>... classes) throws ClassNotFoundException {
Set classNames = new HashSet<>();
// (note that the loader.loadClass() doesn't invoke so we use Class.forName() - this "initialize" flag should do that).
boolean initialize = true;
for (Class> clazz : classes) {
Class> instance = Class.forName(clazz.getName(), initialize, loader);
RuntimeAssertionError.assertTrue(clazz == instance);
String className = Utilities.fullyQualifiedNameToInternalName(clazz.getName());
classNames.add(className.substring(PackageConstants.kShadowSlashPrefix.length()));
}
return classNames;
}
/**
* Returns a deep copy of a class hierarchy that already is populated with all of the shadow
* JCL and API classes.
*/
public ClassHierarchy deepCopyOfClassHierarchy() {
RuntimeAssertionError.assertTrue(this.classHierarchy != null);
return this.classHierarchy.deepCopy();
}
/**
* Computes the object size of shadow java.base classes
*
* @return a mapping between class name and object size
*/
private Map computeRuntimeObjectSizes() {
List classNames = new ArrayList<>();
classNames.addAll(Arrays.stream(this.shadowApiClasses).map(c -> Utilities.fullyQualifiedNameToInternalName(c.getName())).collect(Collectors.toList()));
classNames.addAll(Arrays.stream(this.shadowClasses).map(c -> Utilities.fullyQualifiedNameToInternalName(c.getName())).collect(Collectors.toList()));
Map objectHeapSizeMap = new HashMap<>();
for(String name: classNames){
objectHeapSizeMap.put(name, JCLAndAPIHeapInstanceSize.getAllocationSizeForJCLAndAPISlashClass(name));
}
// add the generated classes, i.e., exceptions in the generated shadow JDK
Stream.of(CommonGenerators.kExceptionClassNames)
.filter(s -> !CommonGenerators.kHandWrittenExceptionClassNames.contains(s))
.map(name -> Utilities.fullyQualifiedNameToInternalName(PackageConstants.kShadowDotPrefix + name))
.forEach(s -> objectHeapSizeMap.put(s, JCLAndAPIHeapInstanceSize.getAllocationSizeForGeneratedExceptionSlashClass()));
return objectHeapSizeMap;
}
private ClassHierarchy buildJCLAndAPIClassHierarchy() {
Map classBytesByQualifiedNames = new HashMap<>();
String mainClassName = "java.lang.Object";
List> classes = new ArrayList<>();
classes.addAll(Arrays.asList(this.shadowApiClasses));
classes.addAll(Arrays.asList(this.shadowClasses));
for (Class> clazz : classes) {
try {
String name = clazz.getName();
InputStream bytecode = clazz.getClassLoader().getResourceAsStream(name.replaceAll("\\.", "/") + ".class");
classBytesByQualifiedNames.put(name, bytecode.readAllBytes());
} catch (IOException e) {
RuntimeAssertionError.unexpected(e);
}
}
LoadedJar runtimeJar = new LoadedJar(classBytesByQualifiedNames, mainClassName);
// Construct the full class hierarchy.
ClassInformationFactory classInfoFactory = new ClassInformationFactory();
Set classInfos = classInfoFactory.fromPostRenameJar(runtimeJar);
return new ClassHierarchyBuilder()
.addPostRenameNonUserDefinedClasses(classInfos)
.build();
}
private Map> getShadowClassSlashNameMethodDescriptorMap(){
try {
return MethodDescriptorCollector.getClassNameMethodDescriptorMap(getJclSlashClassNames(), this.sharedClassLoader);
} catch (ClassNotFoundException e) {
throw RuntimeAssertionError.unexpected(e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy