Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2001-2023 Mathew A. Nelson and Robocode contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://robocode.sourceforge.io/license/epl-v10.html
*/
package net.sf.robocode.host.security;
import net.sf.robocode.core.Container;
import net.sf.robocode.host.IHostedThread;
import net.sf.robocode.host.IRobotClassLoader;
import net.sf.robocode.io.Logger;
import net.sf.robocode.io.RobocodeProperties;
import robocode.robotinterfaces.IBasicRobot;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.security.*;
import java.security.cert.Certificate;
import java.util.*;
/**
* This class loader is used by robots. It isolates classes which belong to robot and load them locally.
* General java classes or robocode.api classes are loaded by parent loader and shared with Robocode engine.
* Attempts to load classes of Robocode engine are blocked.
*
* @author Mathew A. Nelson (original)
* @author Flemming N. Larsen (contributor)
* @author Matthew Reeder (contributor)
* @author Robert D. Maupin (contributor)
* @author Nathaniel Troutman (contributor)
*/
public class RobotClassLoader extends URLClassLoader implements IRobotClassLoader {
static final String UNTRUSTED_URL = "https://robocode.sourceforge.io/untrusted";
private static final PermissionCollection EMPTY_PERMISSIONS = new Permissions();
private final String fullClassName;
private ClassLoader parent;
private CodeSource codeSource;
private IHostedThread robotProxy;
private Class> robotClass;
// Names on classes referenced from the robot class
private Set referencedClasses = new HashSet();
// Cached names on found system classes
private Set foundSystemClasses = new HashSet();
// Cached warning messages
private String[] staticRobotInstanceWarning;
public RobotClassLoader(URL robotClassPath, String robotFullClassName) {
super(new URL[] { robotClassPath}, Container.systemLoader);
fullClassName = robotFullClassName;
parent = getParent();
try {
codeSource = new CodeSource(new URL(UNTRUSTED_URL), (Certificate[]) null);
} catch (MalformedURLException ignored) {}
}
public void setRobotProxy(Object robotProxy) {
this.robotProxy = (IHostedThread) robotProxy;
}
public synchronized void addURL(URL url) {
super.addURL(url);
}
public synchronized Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (RobocodeProperties.isSecurityOn()) {
testPackages(name);
}
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("kotlin.")) {
// we always delegate java.lang and Kotlin stuff to parent loader
return super.loadClass(name, resolve);
}
if (!name.startsWith("robocode")) {
Class> result = loadRobotClassLocally(name, resolve);
if (result != null) {
// yes, it is in robot's class path. We loaded it locally
return result;
}
}
// it is robot API or java class, so we delegate to parent class loader
return parent.loadClass(name);
}
private void testPackages(String name) throws ClassNotFoundException {
if (name.startsWith("net.sf.robocode")) {
String message = "Robots are not allowed to reference Robocode engine in package: net.sf.robocode";
punishSecurityViolation(message);
throw new ClassNotFoundException(message);
}
if (name.startsWith("robocode.control")) {
String message = "Robots are not allowed to reference Robocode engine in package: robocode.control";
punishSecurityViolation(message);
throw new ClassNotFoundException(message);
}
if (RobocodeProperties.isSecurityOn() && name.startsWith("javax.swing")) {
String message = "Robots are not allowed to reference Robocode engine in package: javax.swing";
punishSecurityViolation(message);
throw new ClassNotFoundException(message);
}
}
private Class> loadRobotClassLocally(String name, boolean resolve) throws ClassNotFoundException {
Class> result = findLoadedClass(name);
if (result == null) {
ByteBuffer resource = findLocalResource(name);
if (resource != null) {
result = defineClass(name, resource, codeSource);
if (resolve) {
resolveClass(result);
}
ClassAnalyzer.getReferencedClasses(resource, referencedClasses);
}
}
return result;
}
// this whole fun is there to be able to provide defineClass with bytes
// we need to call defineClass to be able to set codeSource to untrustedLocation
private ByteBuffer findLocalResource(final String name) {
return AccessController.doPrivileged(new PrivilegedAction() {
public ByteBuffer run() {
// try to find it in robot's class path
// this is URL, don't change to File.pathSeparator
String path = name.replace('.', '/').concat(".class");
URL url = findResource(path);
return ClassFileReader.readClassFileFromURL(url);
}
});
}
private void punishSecurityViolation(String message) {
if (robotProxy != null) {
robotProxy.punishSecurityViolation(message);
}
}
protected PermissionCollection getPermissions(CodeSource codesource) {
if (RobocodeProperties.isSecurityOn()) {
return EMPTY_PERMISSIONS;
}
return super.getPermissions(codesource);
}
public String[] getReferencedClasses() {
return referencedClasses.toArray(new String[0]);
}
public synchronized Class> loadRobotMainClass(boolean resolve) throws ClassNotFoundException {
if (fullClassName == null) return null;
try {
if (robotClass == null) {
robotClass = loadClass(fullClassName, resolve);
if (!IBasicRobot.class.isAssignableFrom(robotClass)) {
// that's not robot
return null;
}
if (resolve) {
// resolve methods to see more referenced classes
robotClass.getMethods();
// iterate thru dependencies until we didn't found any new
HashSet clone;
do {
clone = new HashSet(referencedClasses);
for (String reference : clone) {
testPackages(reference);
if (!isSystemClass(reference)) {
loadClass(reference, true);
}
}
} while (referencedClasses.size() != clone.size());
}
} else {
warnIfStaticRobotInstanceFields();
}
} catch (Throwable e) {
robotClass = null;
throw new ClassNotFoundException(e.getMessage(), e);
}
return robotClass;
}
public IBasicRobot createRobotInstance() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
loadRobotMainClass(true);
return (IBasicRobot) robotClass.newInstance();
}
public void cleanup() {
// Bug fix [2930266] - Robot static data isn't being GCed after battle
for (String className : getReferencedClasses()) {
cleanStaticReferences(className);
}
parent = null;
codeSource = null;
robotProxy = null;
robotClass = null;
referencedClasses = null;
foundSystemClasses = null;
}
/**
* Cleans all static field references on a class.
*
* @param className the name of the class containing the static references to clean.
*/
private void cleanStaticReferences(String className) {
if (isSystemClass(className)) {
return;
}
Class> type = null;
try {
type = loadRobotClassLocally(className, false);
if (type != null) {
for (Field field : getAllFields(new ArrayList(), type)) {
if (isStaticReference(field)) {
cleanStaticReference(field);
}
}
}
} catch (Throwable t) {
Logger.logError(t);
}
}
private void warnIfStaticRobotInstanceFields() {
if (staticRobotInstanceWarning == null) {
List staticRobotReferences = new ArrayList();
for (String className : getReferencedClasses()) { // Bug fix [3028102] - ConcurrentModificationException
if (isSystemClass(className)) {
continue;
}
Class> type = null;
try {
type = loadRobotClassLocally(className, false);
} catch (Throwable t) {
continue;
}
if (type != null) {
for (Field field : getAllFields(new ArrayList(), type)) {
if (isStaticReference(field) && IBasicRobot.class.isAssignableFrom(field.getType())
&& field.getAnnotation(robocode.annotation.SafeStatic.class) == null) {
staticRobotReferences.add(field);
}
}
}
}
if (!staticRobotReferences.isEmpty()) {
StringBuilder buf = new StringBuilder();
buf.append("Warning: ").append(fullClassName).append(
" uses static reference to a robot with the following field(s):");
for (Field field : staticRobotReferences) {
buf.append("\n\t").append(field.getDeclaringClass().getName()).append('.').append(field.getName()).append(", which points to a ").append(
field.getType().getName());
}
staticRobotInstanceWarning = new String[] {
buf.toString(),
"Static references to robots can cause unwanted behaviour with the robot using these.",
"Please change static robot references to non-static references and recompile the robot."};
} else {
staticRobotInstanceWarning = new String[] {}; // Signal that there is no warnings to cache
}
} else if (staticRobotInstanceWarning.length == 0) {
return; // Return, as no warnings should be written out in the robot console
}
// Write out warnings to the robot console
if (robotProxy != null) {
for (String line : staticRobotInstanceWarning) {
robotProxy.getOut().println("SYSTEM: " + line);
}
}
}
/**
* Cleans a static field reference class, even when it is 'private static final'
*
* @param field the field to clean, if it is a static reference.
*/
private void cleanStaticReference(Field field) {
if (field.getName().startsWith("const__")) {
// Ignore 'const__' fields used with e.g. the Clojure language
return;
}
field.setAccessible(true);
try {
// In order to set a 'private static field', we need to fix the modifier, i.e. use magic! ;-)
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
int modifiers = modifiersField.getInt(field);
modifiersField.setInt(field, modifiers & ~Modifier.FINAL); // Remove the FINAL modifier
field.set(null, null);
} catch (Throwable ignore) {}
}
/**
* Gets all fields of a class (public, protected, private) and the ones inherited from all super classes.
* @param fields the list where the fields will be added as a result of calling this method.
* @param type the class to retrieve all the fields from
* @return the list specified as input parameter containing all the retrieved fields
*/
private List getAllFields(List fields, Class> type) {
if (type == null || isSystemClass(type.getName())) {
return fields;
}
try {
Collections.addAll(fields, type.getDeclaredFields());
} catch (Throwable ignore) {// NoClassDefFoundError does occur with some robots, e.g. sgp.Drunken [1.12]
// We ignore all exceptions and errors here, so we can proceed to retrieve
// field from super classes.
}
if (type.getSuperclass() != null) {
fields = getAllFields(fields, type.getSuperclass());
}
return fields;
}
/**
* Checks if a specified class name is a Java system class or internal Robocode class.
* @param className the class name to check.
* @return true if the class name is a system class; false otherwise.
*/
private boolean isSystemClass(String className) {
boolean isSystemClass = foundSystemClasses.contains(className);
if (!isSystemClass) {
try {
if (findSystemClass(className) != null) {
foundSystemClasses.add(className);
isSystemClass = true;
}
} catch (ClassNotFoundException ignore) {}
}
return isSystemClass;
}
/**
* Checks if a specified field is a static reference.
*
* @param field the field to check.
* @return true if the field is static reference; false otherwise.
*/
private static boolean isStaticReference(Field field) {
return Modifier.isStatic(field.getModifiers())
&& !(field.getType().isPrimitive() || field.isEnumConstant() || field.isSynthetic());
}
// Work-round for bug 389: Third-party team JARs broken with Java 9
// The URLClassLoader.findResource() in Java 9 returns null with some robot JARs
@Override
public URL findResource(String name) {
URL url = super.findResource(name);
if (url == null) {
// Ignore internal Java and Robocode classes
if (name.startsWith("jdk/") || name.startsWith("java/") || name.startsWith("javax/") || name.startsWith("sun/") || name.startsWith("net.sf.robocode/")) {
return null;
}
URL[] urls = getURLs();
if (urls != null) {
for (URL u : urls) {
if (u != null) {
try {
URL tmp = new URL(u.getProtocol(), u.getHost(), u.getPort(), u.getPath() + name);
if (u.openConnection() != null) {
return tmp;
}
} catch (IOException ignore) {
}
}
}
}
}
return url;
}
}