blob: eceea00177845eaf64641db4203c3ea85a77cc11 [file] [log] [blame] [edit]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#ifndef ART_RUNTIME_CLASS_LOADER_CONTEXT_H_
#define ART_RUNTIME_CLASS_LOADER_CONTEXT_H_
#include <string>
#include <vector>
#include <set>
#include "arch/instruction_set.h"
#include "base/dchecked_vector.h"
#include "dex/dex_file.h"
#include "handle_scope.h"
#include "mirror/class_loader.h"
#include "oat_file.h"
#include "scoped_thread_state_change.h"
namespace art {
class DexFile;
class OatFile;
// Utility class which holds the class loader context used during compilation/verification.
class ClassLoaderContext {
public:
enum class VerificationResult {
kVerifies,
kMismatch,
};
enum ClassLoaderType {
kInvalidClassLoader = 0,
kPathClassLoader = 1,
kDelegateLastClassLoader = 2,
kInMemoryDexClassLoader = 3
};
// Special encoding used to denote a foreign ClassLoader was found when trying to encode class
// loader contexts for each classpath element in a ClassLoader. See
// EncodeClassPathContextsForClassLoader. Keep in sync with PackageDexUsage in the framework.
static constexpr const char* kUnsupportedClassLoaderContextEncoding =
"=UnsupportedClassLoaderContext=";
~ClassLoaderContext();
// Opens requested class path files and appends them to ClassLoaderInfo::opened_dex_files.
// If the dex files have been stripped, the method opens them from their oat files which are added
// to ClassLoaderInfo::opened_oat_files. The 'classpath_dir' argument specifies the directory to
// use for the relative class paths.
// Returns true if all dex files where successfully opened.
// It may be called only once per ClassLoaderContext. Subsequent calls will return the same
// result without doing anything.
// If `context_fds` is an empty vector, files will be opened using the class path locations as
// filenames. Otherwise `context_fds` is expected to contain file descriptors to class path dex
// files, following the order of dex file locations in a flattened class loader context. If their
// number (size of `context_fds`) does not match the number of dex files, OpenDexFiles will fail.
//
// This will replace the class path locations with the locations of the opened dex files.
// (Note that one dex file can contain multidexes. Each multidex will be added to the classpath
// separately.)
//
// only_read_checksums controls whether or not we only read the dex locations and the checksums
// from the apk instead of fully opening the dex files.
//
// This method is not thread safe.
//
// Note that a "false" return could mean that either an apk/jar contained no dex files or
// that we hit a I/O or checksum mismatch error.
// TODO(calin): Currently there's no easy way to tell the difference.
//
// TODO(calin): we're forced to complicate the flow in this class with a different
// OpenDexFiles step because the current dex2oat flow requires the dex files be opened before
// the class loader is created. Consider reworking the dex2oat part.
bool OpenDexFiles(const std::string& classpath_dir = "",
const std::vector<int>& context_fds = std::vector<int>(),
bool only_read_checksums = false);
// Remove the specified compilation sources from all classpaths present in this context.
// Should only be called before the first call to OpenDexFiles().
bool RemoveLocationsFromClassPaths(const dchecked_vector<std::string>& compilation_sources);
// Creates the entire class loader hierarchy according to the current context.
// Returns the first class loader from the chain.
//
// For example: if the context was built from the spec
// "ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]..."
// the method returns the class loader correponding to ClassLoader1. The parent chain will be
// ClassLoader1 --> ClassLoader2 --> ... --> BootClassLoader.
//
// The compilation sources are appended to the classpath of the first class loader (in the above
// example ClassLoader1).
//
// If the context is empty, this method only creates a single PathClassLoader with the
// given compilation_sources.
//
// Shared libraries found in the chain will be canonicalized based on the dex files they
// contain.
//
// Implementation notes:
// 1) the objects are not completely set up. Do not use this outside of tests and the compiler.
// 2) should only be called before the first call to OpenDexFiles().
jobject CreateClassLoader(const std::vector<const DexFile*>& compilation_sources) const;
// Encodes the context as a string suitable to be added in oat files.
// (so that it can be read and verified at runtime against the actual class
// loader hierarchy).
// Should only be called if OpenDexFiles() returned true.
// If stored context is non-null, the stored names are overwritten by the class path from the
// stored context.
// E.g. if the context is PCL[a.dex:b.dex] this will return
// "PCL[a.dex*a_checksum*b.dex*a_checksum]".
std::string EncodeContextForOatFile(const std::string& base_dir,
ClassLoaderContext* stored_context = nullptr) const;
// Encodes the context as a string suitable to be passed to dex2oat.
// This is the same as EncodeContextForOatFile but without adding the checksums
// and only adding each dex files once (no multidex).
// Should only be called if OpenDexFiles() returned true.
std::string EncodeContextForDex2oat(const std::string& base_dir) const;
// Encodes the contexts for each of the classpath elements in the child-most
// classloader. Under the hood EncodeContextForDex2oat is used, so no checksums
// will be encoded.
// Should only be called if the dex files are opened (either via OpenDexFiles() or by creating the
// context from a live class loader).
// Notably, for each classpath element the encoded classloader context will contain only the
// elements that appear before it in the containing classloader. E.g. if `this` contains
// (from child to parent):
//
// PathClassLoader { multidex.apk!classes.dex, multidex.apk!classes2.dex, foo.dex, bar.dex } ->
// PathClassLoader { baz.dex } -> BootClassLoader
//
// then the return value will look like:
//
// `{ "multidex.apk": "PCL[];PCL[baz.dex]",
// "foo.dex" : "PCL[multidex.apk];PCL[baz.dex]",
// "bar.dex" : "PCL[multidex.apk:foo.dex];PCL[baz.dex]" }`
std::map<std::string, std::string> EncodeClassPathContexts(const std::string& base_dir) const;
// Flattens the opened dex files into the given vector.
// Should only be called if OpenDexFiles() returned true.
std::vector<const DexFile*> FlattenOpenedDexFiles() const;
// Return a colon-separated list of dex file locations from this class loader
// context after flattening.
std::string FlattenDexPaths() const;
// Verifies that the current context is identical to the context encoded as `context_spec`.
// Identical means:
// - the number and type of the class loaders from the chain matches
// - the class loader from the same position have the same classpath
// (the order and checksum of the dex files matches)
// This should be called after OpenDexFiles() with only_read_checksums=true. There's no
// need to fully open the dex files if the only thing that needs to be done is to verify
// the context.
//
// Names are only verified if verify_names is true.
// Checksums are only verified if verify_checksums is true.
VerificationResult VerifyClassLoaderContextMatch(const std::string& context_spec,
bool verify_names = true,
bool verify_checksums = true) const;
// Checks if any of the given dex files is already loaded in the current class loader context.
// It only checks the first class loader.
// Returns the list of duplicate dex files (empty if there are no duplicates).
std::set<const DexFile*> CheckForDuplicateDexFiles(
const std::vector<const DexFile*>& dex_files);
// Creates the class loader context from the given string.
// The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...
// ClassLoaderType is either "PCL" (PathClassLoader) or "DLC" (DelegateLastClassLoader).
// ClasspathElem is the path of dex/jar/apk file.
//
// The spec represents a class loader chain with the natural interpretation:
// ClassLoader1 has ClassLoader2 as parent which has ClassLoader3 as a parent and so on.
// The last class loader is assumed to have the BootClassLoader as a parent.
//
// Note that we allowed class loaders with an empty class path in order to support a custom
// class loader for the source dex files.
static std::unique_ptr<ClassLoaderContext> Create(const std::string& spec);
// Creates a context for the given class_loader and dex_elements.
// The method will walk the parent chain starting from `class_loader` and add their dex files
// to the current class loaders chain. The `dex_elements` will be added at the end of the
// classpath belonging to the `class_loader` argument.
// The ownership of the opened dex files will be retained by the given `class_loader`.
// If there are errors in processing the class loader chain (e.g. unsupported elements) the
// method returns null.
static std::unique_ptr<ClassLoaderContext> CreateContextForClassLoader(jobject class_loader,
jobjectArray dex_elements);
// Returns the default class loader context to be used when none is specified.
// This will return a context with a single and empty PathClassLoader.
static std::unique_ptr<ClassLoaderContext> Default();
// Encodes the contexts for each of the classpath elements in `class_loader`. See
// ClassLoaderContext::EncodeClassPathContexts for more information about the return value.
//
// If `class_loader` does not derive from BaseDexClassLoader then an empty map is returned.
// Otherwise if a foreign ClassLoader is found in the class loader chain then the results values
// will all be ClassLoaderContext::kUnsupportedClassLoaderContextEncoding.
static std::map<std::string, std::string> EncodeClassPathContextsForClassLoader(
jobject class_loader);
// Returns whether `encoded_class_loader_context` is a valid encoded ClassLoaderContext or
// EncodedUnsupportedClassLoaderContext.
static bool IsValidEncoding(const std::string& possible_encoded_class_loader_context);
struct ClassLoaderInfo {
// The type of this class loader.
ClassLoaderType type;
// Shared libraries this context has.
std::vector<std::unique_ptr<ClassLoaderInfo>> shared_libraries;
// Shared libraries that will be loaded after apks code that this context has.
std::vector<std::unique_ptr<ClassLoaderInfo>> shared_libraries_after;
// The list of class path elements that this loader loads.
// Note that this list may contain relative paths.
std::vector<std::string> classpath;
// Original opened class path (ignoring multidex).
std::vector<std::string> original_classpath;
// The list of class path elements checksums.
// May be empty if the checksums are not given when the context is created.
std::vector<uint32_t> checksums;
// After OpenDexFiles is called this holds the opened dex files.
std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
// After OpenDexFiles, in case some of the dex files were opened from their oat files
// this holds the list of opened oat files.
std::vector<std::unique_ptr<OatFile>> opened_oat_files;
// The parent class loader.
std::unique_ptr<ClassLoaderInfo> parent;
explicit ClassLoaderInfo(ClassLoaderType cl_type) : type(cl_type) {}
};
private:
// Creates an empty context (with no class loaders).
ClassLoaderContext();
// Get the parent of the class loader chain at depth `index`.
ClassLoaderInfo* GetParent(size_t index) const {
ClassLoaderInfo* result = class_loader_chain_.get();
while ((result != nullptr) && (index-- != 0)) {
result = result->parent.get();
}
return result;
}
size_t GetParentChainSize() const {
size_t result = 0;
ClassLoaderInfo* info = class_loader_chain_.get();
while (info != nullptr) {
++result;
info = info->parent.get();
}
return result;
}
// Constructs an empty context.
// `owns_the_dex_files` specifies whether or not the context will own the opened dex files
// present in the class loader chain. If `owns_the_dex_files` is true then OpenDexFiles cannot
// be called on this context (dex_files_open_attempted_ and dex_files_open_result_ will be set
// to true as well)
explicit ClassLoaderContext(bool owns_the_dex_files);
// Reads the class loader spec in place and returns true if the spec is valid and the
// compilation context was constructed.
bool Parse(const std::string& spec, bool parse_checksums = false);
ClassLoaderInfo* ParseInternal(const std::string& spec, bool parse_checksums);
// Attempts to parse a single class loader spec.
// Returns the ClassLoaderInfo abstraction for this spec, or null if it cannot be parsed.
std::unique_ptr<ClassLoaderInfo> ParseClassLoaderSpec(
const std::string& class_loader_spec,
bool parse_checksums = false);
// CHECKs that the dex files were opened (OpenDexFiles was called and set dex_files_open_result_
// to true). Aborts if not. The `calling_method` is used in the log message to identify the source
// of the call.
void CheckDexFilesOpened(const std::string& calling_method) const;
// Creates the `ClassLoaderInfo` representing`class_loader` and attach it to `this`.
// The dex file present in `dex_elements` array (if not null) will be added at the end of
// the classpath.
bool CreateInfoFromClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
Handle<mirror::ClassLoader> class_loader,
Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
ClassLoaderInfo* child_info,
bool is_shared_library,
bool is_after)
REQUIRES_SHARED(Locks::mutator_lock_);
// Encodes the context as a string suitable to be passed to dex2oat or to be added to the
// oat file as the class path key.
// If for_dex2oat is true, the encoding adds each file once (i.e. it does not add multidex
// location). Otherwise, for oat files, the encoding adds all the dex files (including multidex)
// together with their checksums.
// Should only be called if OpenDexFiles() returned true.
std::string EncodeContext(const std::string& base_dir,
bool for_dex2oat,
ClassLoaderContext* stored_context) const;
// Internal version of `EncodeContext`, which will be called recursively
// on the parent and shared libraries.
void EncodeContextInternal(const ClassLoaderInfo& info,
const std::string& base_dir,
bool for_dex2oat,
ClassLoaderInfo* stored_info,
std::ostringstream& out) const;
// Encodes e.g. PCL[foo.dex:bar.dex]
void EncodeClassPath(const std::string& base_dir,
const std::vector<std::string>& dex_locations,
const std::vector<uint32_t>& checksums,
ClassLoaderType type,
std::ostringstream& out) const;
// Encodes the shared libraries classloaders and the parent classloader if
// either are present in info, e.g. {PCL[foo.dex]#PCL[bar.dex]};PCL[baz.dex]
void EncodeSharedLibAndParent(const ClassLoaderInfo& info,
const std::string& base_dir,
bool for_dex2oat,
ClassLoaderInfo* stored_info,
std::ostringstream& out) const;
bool ClassLoaderInfoMatch(const ClassLoaderInfo& info,
const ClassLoaderInfo& expected_info,
const std::string& context_spec,
bool verify_names,
bool verify_checksums) const;
// Extracts the class loader type from the given spec.
// Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not
// recognized.
static ClassLoaderType ExtractClassLoaderType(const std::string& class_loader_spec);
// Returns the string representation of the class loader type.
// The returned format can be used when parsing a context spec.
static const char* GetClassLoaderTypeName(ClassLoaderType type);
// Encodes the state of processing the dex files associated with the context.
enum ContextDexFilesState {
// The dex files are not opened.
kDexFilesNotOpened = 1,
// The dex checksums/locations were read from the apk/dex but the dex files were not opened.
kDexFilesChecksumsRead = 2,
// The dex files are opened (either because we called OpenDexFiles, or we used a class loader
// to create the context). This implies kDexFilesChecksumsRead.
kDexFilesOpened = 3,
// We failed to open the dex files or read the checksums.
kDexFilesOpenFailed = 4
};
// The class loader chain.
std::unique_ptr<ClassLoaderInfo> class_loader_chain_;
// The opening state of the dex files.
ContextDexFilesState dex_files_state_;
// Whether or not the context owns the opened dex and oat files.
// If true, the opened dex files will be de-allocated when the context is destructed.
// If false, the objects will continue to be alive.
// Note that for convenience the the opened dex/oat files are stored as unique pointers
// which will release their ownership in the destructor based on this flag.
const bool owns_the_dex_files_;
friend class ClassLoaderContextTest;
DISALLOW_COPY_AND_ASSIGN(ClassLoaderContext);
};
} // namespace art
#endif // ART_RUNTIME_CLASS_LOADER_CONTEXT_H_