io.hotmoka.whitelisting.internal.WhiteListingWizardImpl Maven / Gradle / Ivy
/*
Copyright 2021 Fausto Spoto
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 io.hotmoka.whitelisting.internal;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Optional;
import io.hotmoka.whitelisting.MissingWhiteListingAnnotationsError;
import io.hotmoka.whitelisting.WhiteListingWizard;
/**
* A sealed implementation of a white-listing wizard.
*/
class WhiteListingWizardImpl implements WhiteListingWizard {
private final static String WHITE_LISTED_ROOT = WhiteListingWizardImpl.class.getPackage().getName() + ".database";
/**
* The class loader used to load the classes whose code is checked for white-listing.
*/
private final ResolvingClassLoaderImpl classLoader;
/**
* The package name where the white-listing annotations are looked for.
*/
private final String whiteListedRootWithVersion;
/**
* Builds a wizard.
*
* @param classLoader the class loader used to load the classes whose code is checked for white-listing
*/
WhiteListingWizardImpl(ResolvingClassLoaderImpl classLoader) {
this.classLoader = classLoader;
this.whiteListedRootWithVersion = WHITE_LISTED_ROOT + ".version" + classLoader.getVerificationVersion() + ".";
ensureVerificationVersionExistsInDatabase();
}
/**
* Tries to load the white-listing annotations of class java.lang.Object from the database.
*
* @throws MissingWhiteListingAnnotationsError if the annotations cannot be found in the database
*/
private void ensureVerificationVersionExistsInDatabase() {
try {
classLoader.loadClass(whiteListedRootWithVersion + Object.class.getName());
}
catch (ClassNotFoundException e) {
throw new MissingWhiteListingAnnotationsError(classLoader.getVerificationVersion());
}
}
@Override
public Optional whiteListingModelOf(Field field) {
// if the class defining the field has been loaded by the blockchain class loader,
// then it comes from blockchain and the field is white-listed
if (field.getDeclaringClass().getClassLoader() == classLoader)
return Optional.of(field);
else
return fieldInWhiteListedLibraryFor(field);
}
@Override
public Optional> whiteListingModelOf(Constructor> constructor) {
// if the class defining the constructor has been loaded by the blockchain class loader,
// then it comes from blockchain and the constructor is white-listed
Class> declaringClass = constructor.getDeclaringClass();
if (declaringClass.getClassLoader() == classLoader)
return Optional.of(constructor);
else
return constructorInWhiteListedLibraryFor(constructor);
}
@Override
public Optional whiteListingModelOf(Method method) {
Class> declaringClass = method.getDeclaringClass();
if (declaringClass.getClassLoader() == classLoader)
// if the class defining the method has been loaded by the blockchain class loader,
// then it comes from blockchain and the method is white-listed
return Optional.of(method);
else {
// otherwise we check in the possibly overridden methods
Optional result = methodInWhiteListedLibraryFor(method);
if (result.isPresent())
return result;
// a method might not be explicitly white-listed, but it might override a method
// of a superclass that is white-listed. Hence we check that possibility
if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isPrivate(method.getModifiers())) {
Class> superclass = declaringClass.getSuperclass();
// all interfaces extend Object
if (superclass == null && declaringClass.isInterface())
superclass = Object.class;
if (superclass != null) {
Optional overridden = classLoader.resolveMethod(superclass, method.getName(), method.getParameterTypes(), method.getReturnType());
if (overridden.isPresent()) {
result = whiteListingModelOf(overridden.get());
if (result.isPresent())
return result;
}
}
for (Class> superinterface: declaringClass.getInterfaces()) {
Optional overridden = classLoader.resolveMethod(superinterface, method.getName(), method.getParameterTypes(), method.getReturnType());
if (overridden.isPresent()) {
result = whiteListingModelOf(overridden.get());
if (result.isPresent())
return result;
}
}
}
}
return Optional.empty();
}
private Optional fieldInWhiteListedLibraryFor(Field field) {
try {
return classLoader.resolveField(mirrorClassNameFor(field), field.getName(), field.getType());
}
catch (ClassNotFoundException e) {
// the field is not in the library of white-listed code
return Optional.empty();
}
}
private Optional> constructorInWhiteListedLibraryFor(Constructor> constructor) {
try {
return classLoader.resolveConstructor(mirrorClassNameFor(constructor), constructor.getParameterTypes());
}
catch (ClassNotFoundException e) {
// the constructor is not in the library of white-listed code
return Optional.empty();
}
}
private Optional methodInWhiteListedLibraryFor(java.lang.reflect.Method method) {
// Method Object.getClass() is white-listed but we cannot put it in the white-listed library, since that method is final in Object
if (method.getDeclaringClass() == Object.class && "getClass".equals(method.getName()))
try {
return Optional.of(Object.class.getMethod("getClass"));
}
catch (NoSuchMethodException | SecurityException e) {
// this will never happen
throw new IllegalStateException("Cannot access method Object.getClass()");
}
try {
return classLoader.resolveMethodExact(mirrorClassNameFor(method), method.getName(), method.getParameterTypes(), method.getReturnType());
}
catch (ClassNotFoundException e) {
// the method is not in the library of white-listed code
return Optional.empty();
}
}
/**
* Yields the name of the mirror of the class of the given member, in the white-listing database.
* @param member the member
* @return the name of the mirror class
*/
private String mirrorClassNameFor(Member member) {
return whiteListedRootWithVersion + member.getDeclaringClass().getName();
}
}