blob: 89609baf79b6ccea0d72f939732a155533a736bc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2017 IBM Corporation and others.
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.weaving;
import java.security.*;
import java.util.*;
import org.eclipse.osgi.container.ModuleRevision;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook;
import org.eclipse.osgi.internal.loader.BundleLoader;
import org.eclipse.osgi.internal.loader.classpath.ClasspathEntry;
import org.eclipse.osgi.internal.permadmin.BundlePermissions;
import org.eclipse.osgi.internal.serviceregistry.HookContext;
import org.eclipse.osgi.internal.serviceregistry.ServiceRegistry;
import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.eclipse.osgi.storage.StorageUtil;
import org.eclipse.osgi.storage.bundlefile.BundleEntry;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.*;
import org.osgi.framework.hooks.weaving.*;
import org.osgi.framework.wiring.BundleWiring;
public final class WovenClassImpl implements WovenClass, HookContext {
private final static byte FLAG_HOOKCALLED = 0x01;
private final static byte FLAG_HOOKSCOMPLETE = 0x02;
private final static byte FLAG_WEAVINGCOMPLETE = 0x04;
private final static String weavingHookName = WeavingHook.class.getName();
private final String className;
private final BundleEntry entry;
private final List<String> dynamicImports;
private final ClasspathEntry classpathEntry;
private final BundleLoader loader;
final ServiceRegistry registry;
private final Map<ServiceRegistration<?>, Boolean> blackList;
private byte[] validBytes;
private byte[] resultBytes;
private byte hookFlags = 0;
private Throwable error;
private ServiceRegistration<?> errorHook;
private Class<?> clazz;
private int state;
final EquinoxContainer container;
public WovenClassImpl(String className, byte[] bytes, BundleEntry entry, ClasspathEntry classpathEntry, BundleLoader loader, EquinoxContainer container, Map<ServiceRegistration<?>, Boolean> blacklist) {
super();
this.className = className;
this.validBytes = this.resultBytes = bytes;
this.entry = entry;
this.dynamicImports = new DynamicImportList(this);
this.classpathEntry = classpathEntry;
this.loader = loader;
this.registry = container.getServiceRegistry();
this.container = container;
this.blackList = blacklist;
setState(TRANSFORMING);
}
public byte[] getBytes() {
if ((hookFlags & FLAG_HOOKSCOMPLETE) == 0) {
checkPermission();
return validBytes; // return raw bytes until complete
}
// we have called all hooks; someone is calling outside of weave call
// need to be safe and copy the bytes.
byte[] current = validBytes;
byte[] results = new byte[current.length];
System.arraycopy(current, 0, results, 0, current.length);
return results;
}
public void setBytes(byte[] newBytes) {
checkPermission();
if (newBytes == null)
throw new NullPointerException("newBytes cannot be null."); //$NON-NLS-1$
if ((hookFlags & FLAG_HOOKSCOMPLETE) != 0)
// someone is calling this outside of weave
throw new IllegalStateException("Weaving has completed already."); //$NON-NLS-1$
this.resultBytes = this.validBytes = newBytes;
}
void checkPermission() {
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AdminPermission(loader.getWiring().getBundle(), AdminPermission.WEAVE));
}
public List<String> getDynamicImports() {
if ((hookFlags & FLAG_HOOKSCOMPLETE) == 0)
return dynamicImports;
// being called outside of weave; return unmodified list
return Collections.unmodifiableList(dynamicImports);
}
public boolean isWeavingComplete() {
return (hookFlags & FLAG_WEAVINGCOMPLETE) != 0;
}
private void setHooksComplete() {
// create a copy of the bytes array that noone has a reference to
byte[] original = validBytes;
validBytes = new byte[original.length];
System.arraycopy(original, 0, validBytes, 0, original.length);
hookFlags |= FLAG_HOOKSCOMPLETE;
}
void setWeavingCompleted(Class<?> clazz) {
// weaving has completed; save the class and mark complete
this.clazz = clazz;
hookFlags |= FLAG_WEAVINGCOMPLETE;
// Only notify listeners if weaving hooks were called.
if ((hookFlags & FLAG_HOOKCALLED) == 0)
return;
// Only notify listeners if they haven't already been notified of
// the terminal TRANSFORMING_FAILED state.
if (error != null)
return;
// If clazz is null, a class definition failure occurred.
setState(clazz == null ? DEFINE_FAILED : DEFINED);
notifyWovenClassListeners();
}
public String getClassName() {
return className;
}
public ProtectionDomain getProtectionDomain() {
return classpathEntry.getDomain();
}
public Class<?> getDefinedClass() {
return clazz;
}
public BundleWiring getBundleWiring() {
return loader.getWiring();
}
public void call(final Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
if (error != null)
return; // do not call any other hooks once an error has occurred.
if (hook instanceof WeavingHook) {
if (skipRegistration(hookRegistration)) {
// Note we double check blacklist here just
// in case another thread blacklisted since the first check
return;
}
if ((hookFlags & FLAG_HOOKCALLED) == 0) {
hookFlags |= FLAG_HOOKCALLED;
// only do this check on the first weaving hook call
if (!validBytes(validBytes)) {
validBytes = StorageUtil.getBytes(entry.getInputStream(), (int) entry.getSize(), 8 * 1024);
}
}
try {
((WeavingHook) hook).weave(this);
} catch (WeavingException e) {
error = e;
errorHook = hookRegistration;
// do not blacklist on weaving exceptions
} catch (Throwable t) {
error = t; // save the error to fail later
errorHook = hookRegistration;
// put the registration on the black list
blackList.put(hookRegistration, Boolean.TRUE);
}
}
}
@Override
public boolean skipRegistration(ServiceRegistration<?> hookRegistration) {
return blackList.containsKey(hookRegistration);
}
private boolean validBytes(byte[] checkBytes) {
if (checkBytes == null || checkBytes.length < 4)
return false;
if ((checkBytes[0] & 0xCA) != 0xCA)
return false;
if ((checkBytes[1] & 0xFE) != 0xFE)
return false;
if ((checkBytes[2] & 0xBA) != 0xBA)
return false;
if ((checkBytes[3] & 0xBE) != 0xBE)
return false;
return true;
}
public String getHookMethodName() {
return "weave"; //$NON-NLS-1$
}
public String getHookClassName() {
return weavingHookName;
}
private void notifyWovenClassListeners() {
final HookContext context = new HookContext() {
@Override
public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
if (!(hook instanceof WovenClassListener))
return;
try {
((WovenClassListener) hook).modified(WovenClassImpl.this);
} catch (Exception e) {
WovenClassImpl.this.container.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, hookRegistration.getReference().getBundle(), e);
}
}
@Override
public String getHookClassName() {
return WovenClassListener.class.getName();
}
@Override
public String getHookMethodName() {
return "modified"; //$NON-NLS-1$
}
@Override
public boolean skipRegistration(ServiceRegistration<?> hookRegistration) {
return false;
}
};
if (System.getSecurityManager() == null)
registry.notifyHooksPrivileged(context);
else {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() {
registry.notifyHooksPrivileged(context);
return null;
}
});
} catch (PrivilegedActionException e) {
throw (RuntimeException) e.getException();
}
}
}
byte[] callHooks() throws Throwable {
SecurityManager sm = System.getSecurityManager();
byte[] wovenBytes = null;
List<String> newImports = null;
boolean rejected = false;
try {
if (sm == null) {
registry.notifyHooksPrivileged(this);
} else {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() {
registry.notifyHooksPrivileged(WovenClassImpl.this);
return null;
}
});
} catch (PrivilegedActionException e) {
throw (RuntimeException) e.getException();
}
}
} finally {
if ((hookFlags & FLAG_HOOKCALLED) != 0) {
for (ClassLoaderHook classLoaderHook : container.getConfiguration().getHookRegistry().getClassLoaderHooks()) {
rejected |= classLoaderHook.rejectTransformation(className, resultBytes, classpathEntry, entry, loader.getModuleClassLoader().getClasspathManager());
}
if (!rejected) {
wovenBytes = resultBytes;
newImports = dynamicImports;
}
setHooksComplete();
// Make sure setHooksComplete() has been called. The woven class
// must be immutable in TRANSFORMED or TRANSFORMING_FAILED.
// If error is not null, a weaving hook threw an exception.
setState(error == null ? TRANSFORMED : TRANSFORMING_FAILED);
// only notify listeners if the transformation was not rejected
if (!rejected) {
notifyWovenClassListeners();
}
}
}
if (error != null)
throw error;
if (newImports != null) {
// add any new dynamic imports
for (String newImport : newImports) {
try {
ManifestElement[] importElements = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, newImport);
// Grant implied import package permissions for all dynamic
// import packages to the woven bundle.
addImpliedImportPackagePermissions(importElements);
loader.addDynamicImportPackage(importElements);
} catch (BundleException e) {
// should not have happened; checked at add.
}
}
}
return wovenBytes;
}
private void addImpliedImportPackagePermissions(ManifestElement[] importElements) {
ProtectionDomain wovenDomain = ((Generation) ((ModuleRevision) getBundleWiring().getRevision()).getRevisionInfo()).getDomain();
if (wovenDomain != null) {
// security is enabled; add the permissions
for (ManifestElement clause : importElements)
for (String pkg : clause.getValueComponents())
((BundlePermissions) wovenDomain.getPermissions()).addWovenPermission(new PackagePermission(pkg, PackagePermission.IMPORT));
}
}
public String toString() {
return className;
}
public ServiceRegistration<?> getErrorHook() {
return errorHook;
}
@Override
public int getState() {
return state;
}
private void setState(int value) {
state = value;
}
}