/******************************************************************************* * 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; } }