
foundation.icon.ee.score.Transformer 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 foundation.icon.ee.score;
import foundation.icon.ee.Agent;
import foundation.icon.ee.types.IllegalFormatException;
import i.PackageConstants;
import i.RuntimeAssertionError;
import org.aion.avm.core.AvmConfiguration;
import org.aion.avm.core.ClassHierarchyForest;
import org.aion.avm.core.ClassRenamer;
import org.aion.avm.core.ClassToolchain;
import org.aion.avm.core.ConstantClassBuilder;
import org.aion.avm.core.IExternalState;
import org.aion.avm.core.NodeEnvironment;
import org.aion.avm.core.TypeAwareClassWriter;
import org.aion.avm.core.arraywrapping.ArraysRequiringAnalysisClassVisitor;
import org.aion.avm.core.arraywrapping.ArraysWithKnownTypesClassVisitor;
import org.aion.avm.core.exceptionwrapping.ExceptionWrapping;
import org.aion.avm.core.instrument.ClassMetering;
import org.aion.avm.core.instrument.HeapMemoryCostCalculator;
import org.aion.avm.core.miscvisitors.APIRemapClassVisitor;
import org.aion.avm.core.miscvisitors.ClinitStrippingVisitor;
import org.aion.avm.core.miscvisitors.ConstantVisitor;
import org.aion.avm.core.miscvisitors.InterfaceFieldClassGeneratorVisitor;
import org.aion.avm.core.miscvisitors.InterfaceFieldNameMappingVisitor;
import org.aion.avm.core.miscvisitors.LoopingExceptionStrippingVisitor;
import org.aion.avm.core.miscvisitors.NamespaceMapper;
import org.aion.avm.core.miscvisitors.PreRenameClassAccessRules;
import org.aion.avm.core.miscvisitors.StrictFPVisitor;
import org.aion.avm.core.miscvisitors.UserClassMappingVisitor;
import org.aion.avm.core.persistence.AutomaticGraphVisitor;
import org.aion.avm.core.rejection.ConsensusLimitConstants;
import org.aion.avm.core.rejection.InstanceVariableCountManager;
import org.aion.avm.core.rejection.InstanceVariableCountingVisitor;
import org.aion.avm.core.rejection.RejectedClassException;
import org.aion.avm.core.rejection.RejectionClassVisitor;
import org.aion.avm.core.shadowing.ClassShadowing;
import org.aion.avm.core.shadowing.InvokedynamicShadower;
import org.aion.avm.core.stacktracking.StackWatcherClassAdapter;
import org.aion.avm.core.types.ClassHierarchy;
import org.aion.avm.core.types.ClassInfo;
import org.aion.avm.core.types.Forest;
import org.aion.avm.core.types.GeneratedClassConsumer;
import org.aion.avm.core.types.ImmortalDappModule;
import org.aion.avm.core.types.RawDappModule;
import org.aion.avm.core.types.TransformedDappModule;
import org.aion.avm.core.util.DebugNameResolver;
import org.aion.avm.core.verification.Verifier;
import org.aion.avm.utilities.JarBuilder;
import org.aion.avm.utilities.Utilities;
import org.aion.avm.utilities.analyze.ClassFileInfoBuilder;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Transformer {
/**
* Returns the sizes of all the user-space classes
*
* @param classHierarchy the class hierarchy
* @return The look-up map of the sizes of user objects
*/
private static Map computeUserObjectSizes(Forest classHierarchy)
{
HeapMemoryCostCalculator objectSizeCalculator = new HeapMemoryCostCalculator();
// compute the user object sizes
objectSizeCalculator.calcClassesInstanceSize(classHierarchy);
return objectSizeCalculator.getClassHeapSizeMap();
}
private static Map computeAllPostRenameObjectSizes(Forest forest, boolean preserveDebuggability) {
Map preRenameUserObjectSizes = computeUserObjectSizes(forest);
Map postRenameObjectSizes = new HashMap<>(NodeEnvironment.singleton.postRenameRuntimeObjectSizeMap);
preRenameUserObjectSizes.forEach((k, v) -> postRenameObjectSizes.put(DebugNameResolver.getUserPackageSlashPrefix(k, preserveDebuggability), v));
return postRenameObjectSizes;
}
/**
* Replaces the java.base
package with the shadow implementation.
* Note that this is public since some unit tests call it, directly.
*
* @param inputClasses The class of DApp (names specified in .-style)
* @param oldPreRenameForest The pre-rename forest of user-defined classes in the DApp (/-style).
* @param classHierarchy The class hierarchy of all classes in the system (.-style).
* @param preserveDebuggability Whether or not debug mode is enabled.
* @return the transformed classes and any generated classes (names specified in .-style)
*/
private static Map transformClasses(Map inputClasses, Forest oldPreRenameForest, ClassHierarchy classHierarchy, ClassRenamer classRenamer, boolean preserveDebuggability) {
// Before anything, pass the list of classes through the verifier.
// (this will throw UncaughtException, on verification failure).
Verifier.verifyUntrustedClasses(inputClasses);
// We need to run our rejection filter and static rename pass.
Map safeClasses = rejectionAndRenameInputClasses(inputClasses, classHierarchy, classRenamer, preserveDebuggability);
ConstantClassBuilder.ConstantClassInfo constantClass = ConstantClassBuilder.buildConstantClassBytecodeForClasses(PackageConstants.kConstantClassName, safeClasses.values());
// merge the generated classes and processed classes, assuming the package spaces do not conflict.
Map processedClasses = new HashMap<>();
// Start by adding the constant class.
processedClasses.put(PackageConstants.kConstantClassName, constantClass.bytecode);
// merge the generated classes and processed classes, assuming the package spaces do not conflict.
// We also want to expose this type to the class writer so it can compute common superclasses.
GeneratedClassConsumer generatedClassesSink = (superClassSlashName, classSlashName, bytecode) -> {
// Note that the processed classes are expected to use .-style names.
String classDotName = Utilities.internalNameToFullyQualifiedName(classSlashName);
processedClasses.put(classDotName, bytecode);
};
Map postRenameObjectSizes = computeAllPostRenameObjectSizes(oldPreRenameForest, preserveDebuggability);
Map transformedClasses = new HashMap<>();
int parsingOptions = preserveDebuggability ? ClassReader.EXPAND_FRAMES : ClassReader.EXPAND_FRAMES | ClassReader.SKIP_DEBUG;
for (String name : safeClasses.keySet()) {
// Note that transformClasses requires that the input class names by the .-style names.
RuntimeAssertionError.assertTrue(!name.contains("/"));
// We need to parse with EXPAND_FRAMES, since the StackWatcherClassAdapter uses a MethodNode to parse methods.
// We also add SKIP_DEBUG since we aren't using debug data and skipping it removes extraneous labels which would otherwise
// cause the BlockBuildingMethodVisitor to build lots of small blocks instead of a few big ones (each block incurs a Helper
// static call, which is somewhat expensive - this is how we bill for energy).
var builder = new ClassToolchain.Builder(safeClasses.get(name),
parsingOptions);
Agent agent = Agent.get();
if (agent == null || agent.isClassMeteringEnabled()) {
builder.addNextVisitor(new ClassMetering(postRenameObjectSizes));
}
byte[] bytecode = builder.addNextVisitor(new ConstantVisitor(PackageConstants.kConstantClassName, constantClass.constantToFieldMap))
.addNextVisitor(new InvokedynamicShadower(PackageConstants.kShadowSlashPrefix))
.addNextVisitor(new ClassShadowing(PackageConstants.kShadowSlashPrefix))
.addNextVisitor(new StackWatcherClassAdapter())
.addNextVisitor(new ExceptionWrapping(generatedClassesSink, classHierarchy))
.addNextVisitor(new AutomaticGraphVisitor())
.addNextVisitor(new StrictFPVisitor())
.addWriter(new TypeAwareClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS, classHierarchy, classRenamer))
.build()
.runAndGetBytecode();
bytecode = new ClassToolchain.Builder(bytecode, parsingOptions)
.addNextVisitor(new ArraysRequiringAnalysisClassVisitor(classHierarchy))
.addNextVisitor(new ArraysWithKnownTypesClassVisitor())
.addNextVisitor(new APIRemapClassVisitor())
.addWriter(new TypeAwareClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS, classHierarchy, classRenamer))
.build()
.runAndGetBytecode();
transformedClasses.put(name, bytecode);
}
/*
* Another pass to deal with static fields in interfaces.
* Note that all fields in interfaces are defined as static.
*/
// mapping between interface name and generated class name containing all the interface fields
Map interfaceFieldClassNames = new HashMap<>();
String javaLangObjectSlashName = PackageConstants.kShadowSlashPrefix + "java/lang/Object";
for (String name : transformedClasses.keySet()) {
// This visitor does not modify the byte code of transformedClasses. It only generates a new class containing fields and clinit for each interface.
new ClassReader(transformedClasses.get(name))
.accept(new InterfaceFieldClassGeneratorVisitor(generatedClassesSink, interfaceFieldClassNames, javaLangObjectSlashName), parsingOptions);
}
for (String name : transformedClasses.keySet()) {
byte[] bytecode = new ClassToolchain.Builder(transformedClasses.get(name), parsingOptions)
.addNextVisitor(new InterfaceFieldNameMappingVisitor(interfaceFieldClassNames))
.addWriter(new TypeAwareClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS, classHierarchy, classRenamer))
.build()
.runAndGetBytecode();
processedClasses.put(name, bytecode);
}
return processedClasses;
}
private static Map stripClinitFromClasses(Map transformedClasses){
Map immortalClasses = new HashMap<>();
for (Map.Entry elt : transformedClasses.entrySet()) {
String className = elt.getKey();
byte[] transformedClass = elt.getValue();
byte[] immortalClass = new ClassToolchain.Builder(transformedClass, 0)
.addNextVisitor(new ClinitStrippingVisitor())
.addWriter(new ClassWriter(0))
.build()
.runAndGetBytecode();
immortalClasses.put(className, immortalClass);
}
return immortalClasses;
}
private static Map rejectionAndRenameInputClasses(Map inputClasses, ClassHierarchy classHierarchy, ClassRenamer classRenamer, boolean preserveDebuggability) {
// By this point, we at least know that the classHierarchy is internally consistent.
// This also means we can safely count instance variables to make sure we haven't reached our limit.
InstanceVariableCountManager manager = new InstanceVariableCountManager();
Map safeClasses = new HashMap<>();
Set preRenameUserClassAndInterfaceSet = classHierarchy.getPreRenameUserDefinedClassesAndInterfaces();
Set preRenameUserDefinedClasses = classHierarchy.getPreRenameUserDefinedClassesOnly(classRenamer);
PreRenameClassAccessRules preRenameClassAccessRules = new PreRenameClassAccessRules(preRenameUserDefinedClasses, preRenameUserClassAndInterfaceSet);
NamespaceMapper namespaceMapper = new NamespaceMapper(preRenameClassAccessRules);
for (String name : inputClasses.keySet()) {
// Note that transformClasses requires that the input class names by the .-style names.
RuntimeAssertionError.assertTrue(!name.contains("/"));
int parsingOptions = preserveDebuggability ? 0: ClassReader.SKIP_DEBUG;
try {
byte[] classBytecode = inputClasses.get(name);
// Read the class to check our static geometry limits before running this through our high-level ASM rejection pipeline.
// (note that this processing is done for HistogramDataCollector, back in AvmImpl, but this duplication isn't a large concern since that is disabled, by default).
ClassFileInfoBuilder.ClassFileInfo classFileInfo = ClassFileInfoBuilder.getDirectClassFileInfo(classBytecode);
// Impose class-level restrictions.
if (classFileInfo.definedMethods.size() > ConsensusLimitConstants.MAX_METHOD_COUNT) {
throw RejectedClassException.maximumMethodCountExceeded(name);
}
if (classFileInfo.constantPoolEntryCount > ConsensusLimitConstants.MAX_CONSTANT_POOL_ENTRIES) {
throw RejectedClassException.maximumConstantPoolEntriesExceeded(name);
}
// Impose method-level restrictions.
for (ClassFileInfoBuilder.MethodCode methodCode : classFileInfo.definedMethods) {
if (methodCode.codeLength > ConsensusLimitConstants.MAX_METHOD_BYTE_LENGTH) {
throw RejectedClassException.maximumMethodSizeExceeded(name);
}
if (methodCode.exceptionTableSize > ConsensusLimitConstants.MAX_EXCEPTION_TABLE_ENTRIES) {
throw RejectedClassException.maximumExceptionTableEntriesExceeded(name);
}
if (methodCode.maxStack > ConsensusLimitConstants.MAX_OPERAND_STACK_DEPTH) {
throw RejectedClassException.maximumOperandStackDepthExceeded(name);
}
if (methodCode.maxLocals > ConsensusLimitConstants.MAX_LOCAL_VARIABLES) {
throw RejectedClassException.maximumLocalVariableCountExceeded(name);
}
}
// Now, proceed with the ASM pipeline for high-level rejection and renaming.
InstanceVariableCountingVisitor variableCounter = new InstanceVariableCountingVisitor(manager);
byte[] bytecode = new ClassToolchain.Builder(classBytecode, parsingOptions)
.addNextVisitor(new RejectionClassVisitor(preRenameClassAccessRules, namespaceMapper, preserveDebuggability))
.addNextVisitor(new LoopingExceptionStrippingVisitor())
.addNextVisitor(variableCounter)
.addNextVisitor(new UserClassMappingVisitor(namespaceMapper, preserveDebuggability))
.addWriter(new TypeAwareClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS, classHierarchy, classRenamer))
.build()
.runAndGetBytecode();
String mappedName = DebugNameResolver.getUserPackageDotPrefix(name, preserveDebuggability);
safeClasses.put(mappedName, bytecode);
} catch (Exception e) {
throw new RejectedClassException(e.getMessage());
}
}
// Before we return, make sure we didn't exceed the instance variable limits (will throw RejectedClassException on failure).
manager.verifyAllCounts();
return safeClasses;
}
private final IExternalState es;
private final AvmConfiguration conf;
private byte[] transformedCodeBytes;
private byte[] apisBytes;
private TransformedDappModule bootstrapModule;
public Transformer(IExternalState es, AvmConfiguration conf) {
this.es = es;
this.conf = conf;
}
public void transform() {
try {
transformImpl();
} catch (IOException e) {
RuntimeAssertionError.unexpected(e);
}
}
private void transformImpl() throws IOException {
byte[] codeBytes = es.getCode();
apisBytes = JarBuilder.getAPIsBytesFromJAR(codeBytes);
if (apisBytes == null) {
throw new IllegalFormatException("bad APIS");
}
RawDappModule rawDapp = RawDappModule.readFromJar(codeBytes,
conf.preserveDebuggability,
conf.enableVerboseContractErrors);
if (rawDapp == null) {
throw new IllegalFormatException("bad jar");
}
if (!rawDapp.classes.containsKey(rawDapp.mainClass)) {
throw new IllegalFormatException("no main class");
}
ClassHierarchyForest dappClassesForest = rawDapp.classHierarchyForest;
// transform
Map transformedClasses = transformClasses(
rawDapp.classes, dappClassesForest, rawDapp.classHierarchy,
rawDapp.classRenamer, conf.preserveDebuggability);
bootstrapModule = TransformedDappModule.fromTransformedClasses(transformedClasses, rawDapp.mainClass);
Map immortalClasses = stripClinitFromClasses(transformedClasses);
ImmortalDappModule immortalDapp = ImmortalDappModule.fromImmortalClasses(immortalClasses, bootstrapModule.mainClass, apisBytes);
transformedCodeBytes = immortalDapp.createJar(es.getBlockTimestamp());
}
public TransformedDappModule getBootstrapModule() {
return bootstrapModule;
}
public byte[] getTransformedCodeBytes() {
return transformedCodeBytes;
}
public byte[] getAPIsBytes() {
return apisBytes;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy