Merge "ART: Remove run-test blacklist"
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 6cb8544..5a93e29 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -442,12 +442,51 @@
UNREACHABLE();
}
+// We use the method's DexFile and declaring class name to find the OatMethod for an obsolete
+// method. This is extremely slow but we need it if we want to be able to have obsolete native
+// methods since we need this to find the size of it's stack frames.
+static const OatFile::OatMethod FindOatMethodFromDexFileFor(ArtMethod* method, bool* found)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(method->IsObsolete() && method->IsNative());
+ const DexFile* dex_file = method->GetDexFile();
+
+ // recreate the class_def_index from the descriptor.
+ std::string descriptor_storage;
+ const DexFile::TypeId* declaring_class_type_id =
+ dex_file->FindTypeId(method->GetDeclaringClass()->GetDescriptor(&descriptor_storage));
+ CHECK(declaring_class_type_id != nullptr);
+ dex::TypeIndex declaring_class_type_index = dex_file->GetIndexForTypeId(*declaring_class_type_id);
+ const DexFile::ClassDef* declaring_class_type_def =
+ dex_file->FindClassDef(declaring_class_type_index);
+ CHECK(declaring_class_type_def != nullptr);
+ uint16_t declaring_class_def_index = dex_file->GetIndexForClassDef(*declaring_class_type_def);
+
+ size_t oat_method_index = GetOatMethodIndexFromMethodIndex(*dex_file,
+ declaring_class_def_index,
+ method->GetDexMethodIndex());
+
+ OatFile::OatClass oat_class = OatFile::FindOatClass(*dex_file,
+ declaring_class_def_index,
+ found);
+ if (!(*found)) {
+ return OatFile::OatMethod::Invalid();
+ }
+ return oat_class.GetOatMethod(oat_method_index);
+}
+
static const OatFile::OatMethod FindOatMethodFor(ArtMethod* method,
PointerSize pointer_size,
bool* found)
REQUIRES_SHARED(Locks::mutator_lock_) {
- // We shouldn't be calling this with obsolete methods.
- DCHECK(!method->IsObsolete());
+ if (UNLIKELY(method->IsObsolete())) {
+ // We shouldn't be calling this with obsolete methods except for native obsolete methods for
+ // which we need to use the oat method to figure out how large the quick frame is.
+ DCHECK(method->IsNative()) << "We should only be finding the OatMethod of obsolete methods in "
+ << "order to allow stack walking. Other obsolete methods should "
+ << "never need to access this information.";
+ DCHECK_EQ(pointer_size, kRuntimePointerSize) << "Obsolete method in compiler!";
+ return FindOatMethodFromDexFileFor(method, found);
+ }
// Although we overwrite the trampoline of non-static methods, we may get here via the resolution
// method for direct methods (or virtual methods made direct).
mirror::Class* declaring_class = method->GetDeclaringClass();
diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc
index f59420d..187af4b 100644
--- a/runtime/dex_file.cc
+++ b/runtime/dex_file.cc
@@ -333,7 +333,32 @@
*error_code = ZipOpenErrorCode::kDexFileError;
return nullptr;
}
- std::unique_ptr<MemMap> map(zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg));
+
+ std::unique_ptr<MemMap> map;
+ if (zip_entry->IsUncompressed()) {
+ if (!zip_entry->IsAlignedTo(alignof(Header))) {
+ // Do not mmap unaligned ZIP entries because
+ // doing so would fail dex verification which requires 4 byte alignment.
+ LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; "
+ << "please zipalign to " << alignof(Header) << " bytes. "
+ << "Falling back to extracting file.";
+ } else {
+ // Map uncompressed files within zip as file-backed to avoid a dirty copy.
+ map.reset(zip_entry->MapDirectlyFromFile(location.c_str(), /*out*/error_msg));
+ if (map == nullptr) {
+ LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; "
+ << "is your ZIP file corrupted? Falling back to extraction.";
+ // Try again with Extraction which still has a chance of recovery.
+ }
+ }
+ }
+
+ if (map == nullptr) {
+ // Default path for compressed ZIP entries,
+ // and fallback for stored ZIP entries.
+ map.reset(zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg));
+ }
+
if (map == nullptr) {
*error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", entry_name, location.c_str(),
error_msg->c_str());
@@ -500,6 +525,11 @@
oat_dex_file_(oat_dex_file) {
CHECK(begin_ != nullptr) << GetLocation();
CHECK_GT(size_, 0U) << GetLocation();
+
+ // Check base (=header) alignment.
+ // Must be 4-byte aligned to avoid undefined behavior when accessing
+ // any of the sections via a pointer.
+ CHECK_ALIGNED(begin_, alignof(Header));
}
DexFile::~DexFile() {
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index f11e2cb..d862ff2 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -1010,15 +1010,18 @@
void Instrumentation::ExceptionCaughtEvent(Thread* thread,
mirror::Throwable* exception_object) const {
+ Thread* self = Thread::Current();
+ StackHandleScope<1> hs(self);
+ Handle<mirror::Throwable> h_exception(hs.NewHandle(exception_object));
if (HasExceptionCaughtListeners()) {
- DCHECK_EQ(thread->GetException(), exception_object);
+ DCHECK_EQ(thread->GetException(), h_exception.Get());
thread->ClearException();
for (InstrumentationListener* listener : exception_caught_listeners_) {
if (listener != nullptr) {
- listener->ExceptionCaught(thread, exception_object);
+ listener->ExceptionCaught(thread, h_exception.Get());
}
}
- thread->SetException(exception_object);
+ thread->SetException(h_exception.Get());
}
}
diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h
index e5d34e1..86af6d4 100644
--- a/runtime/jdwp/jdwp.h
+++ b/runtime/jdwp/jdwp.h
@@ -22,6 +22,7 @@
#include "jdwp/jdwp_bits.h"
#include "jdwp/jdwp_constants.h"
#include "jdwp/jdwp_expand_buf.h"
+#include "obj_ptr.h"
#include <pthread.h>
#include <stddef.h>
@@ -286,6 +287,10 @@
REQUIRES(!event_list_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+ void UnregisterLocationEventsOnClass(ObjPtr<mirror::Class> klass)
+ REQUIRES(!event_list_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
/*
* Unregister all events.
*/
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc
index 172f52a..96249f9 100644
--- a/runtime/jdwp/jdwp_event.cc
+++ b/runtime/jdwp/jdwp_event.cc
@@ -251,6 +251,43 @@
return ERR_NONE;
}
+void JdwpState::UnregisterLocationEventsOnClass(ObjPtr<mirror::Class> klass) {
+ VLOG(jdwp) << "Removing events within " << klass->PrettyClass();
+ StackHandleScope<1> hs(Thread::Current());
+ Handle<mirror::Class> h_klass(hs.NewHandle(klass));
+ std::vector<JdwpEvent*> to_remove;
+ MutexLock mu(Thread::Current(), event_list_lock_);
+ for (JdwpEvent* cur_event = event_list_; cur_event != nullptr; cur_event = cur_event->next) {
+ // Fill in the to_remove list
+ bool found_event = false;
+ for (int i = 0; i < cur_event->modCount && !found_event; i++) {
+ JdwpEventMod& mod = cur_event->mods[i];
+ switch (mod.modKind) {
+ case MK_LOCATION_ONLY: {
+ JdwpLocation& loc = mod.locationOnly.loc;
+ JdwpError error;
+ ObjPtr<mirror::Class> breakpoint_class(
+ Dbg::GetObjectRegistry()->Get<art::mirror::Class*>(loc.class_id, &error));
+ DCHECK_EQ(error, ERR_NONE);
+ if (breakpoint_class == h_klass.Get()) {
+ to_remove.push_back(cur_event);
+ found_event = true;
+ }
+ break;
+ }
+ default:
+ // TODO Investigate how we should handle non-locationOnly events.
+ break;
+ }
+ }
+ }
+
+ for (JdwpEvent* event : to_remove) {
+ UnregisterEvent(event);
+ EventFree(event);
+ }
+}
+
/*
* Remove an event from the list. This will also remove the event from
* any optimization tables, e.g. breakpoints.
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index a1efb97..cf3cfa4 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -292,6 +292,10 @@
}
}
+ // To support parallel class-loading, we need to perform some locking dances here. Namely,
+ // the fixup stage must not be holding the temp_classes lock when it fixes up the system
+ // (as that requires suspending all mutators).
+
void AddTempClass(art::Thread* self, jclass klass) {
std::unique_lock<std::mutex> mu(temp_classes_lock);
jclass global_klass = reinterpret_cast<jclass>(self->GetJniEnv()->NewGlobalRef(klass));
@@ -302,18 +306,24 @@
art::Handle<art::mirror::Class> temp_klass,
art::Handle<art::mirror::Class> klass)
REQUIRES_SHARED(art::Locks::mutator_lock_) {
- std::unique_lock<std::mutex> mu(temp_classes_lock);
- if (temp_classes.empty()) {
- return;
- }
-
- for (auto it = temp_classes.begin(); it != temp_classes.end(); ++it) {
- if (temp_klass.Get() == art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(*it))) {
- self->GetJniEnv()->DeleteGlobalRef(*it);
- temp_classes.erase(it);
- FixupTempClass(self, temp_klass, klass);
- break;
+ bool requires_fixup = false;
+ {
+ std::unique_lock<std::mutex> mu(temp_classes_lock);
+ if (temp_classes.empty()) {
+ return;
}
+
+ for (auto it = temp_classes.begin(); it != temp_classes.end(); ++it) {
+ if (temp_klass.Get() == art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(*it))) {
+ self->GetJniEnv()->DeleteGlobalRef(*it);
+ temp_classes.erase(it);
+ requires_fixup = true;
+ break;
+ }
+ }
+ }
+ if (requires_fixup) {
+ FixupTempClass(self, temp_klass, klass);
}
}
@@ -336,50 +346,52 @@
art::mirror::Class* output = klass.Get();
FixupGlobalReferenceTables(input, output);
+ FixupLocalReferenceTables(self, input, output);
}
if (heap->IsGcConcurrentAndMoving()) {
heap->DecrementDisableMovingGC(self);
}
}
+ class RootUpdater : public art::RootVisitor {
+ public:
+ RootUpdater(const art::mirror::Class* input, art::mirror::Class* output)
+ : input_(input), output_(output) {}
+
+ void VisitRoots(art::mirror::Object*** roots,
+ size_t count,
+ const art::RootInfo& info ATTRIBUTE_UNUSED)
+ OVERRIDE {
+ for (size_t i = 0; i != count; ++i) {
+ if (*roots[i] == input_) {
+ *roots[i] = output_;
+ }
+ }
+ }
+
+ void VisitRoots(art::mirror::CompressedReference<art::mirror::Object>** roots,
+ size_t count,
+ const art::RootInfo& info ATTRIBUTE_UNUSED)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ for (size_t i = 0; i != count; ++i) {
+ if (roots[i]->AsMirrorPtr() == input_) {
+ roots[i]->Assign(output_);
+ }
+ }
+ }
+
+ private:
+ const art::mirror::Class* input_;
+ art::mirror::Class* output_;
+ };
+
void FixupGlobalReferenceTables(art::mirror::Class* input,
art::mirror::Class* output)
REQUIRES(art::Locks::mutator_lock_) {
art::JavaVMExt* java_vm = art::Runtime::Current()->GetJavaVM();
// Fix up the global table with a root visitor.
- class GlobalUpdate : public art::RootVisitor {
- public:
- GlobalUpdate(art::mirror::Class* root_input, art::mirror::Class* root_output)
- : input_(root_input), output_(root_output) {}
-
- void VisitRoots(art::mirror::Object*** roots,
- size_t count,
- const art::RootInfo& info ATTRIBUTE_UNUSED)
- OVERRIDE {
- for (size_t i = 0; i != count; ++i) {
- if (*roots[i] == input_) {
- *roots[i] = output_;
- }
- }
- }
-
- void VisitRoots(art::mirror::CompressedReference<art::mirror::Object>** roots,
- size_t count,
- const art::RootInfo& info ATTRIBUTE_UNUSED)
- OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
- for (size_t i = 0; i != count; ++i) {
- if (roots[i]->AsMirrorPtr() == input_) {
- roots[i]->Assign(output_);
- }
- }
- }
-
- private:
- const art::mirror::Class* input_;
- art::mirror::Class* output_;
- };
- GlobalUpdate global_update(input, output);
+ RootUpdater global_update(input, output);
java_vm->VisitRoots(&global_update);
class WeakGlobalUpdate : public art::IsMarkedVisitor {
@@ -402,6 +414,33 @@
java_vm->SweepJniWeakGlobals(&weak_global_update);
}
+ void FixupLocalReferenceTables(art::Thread* self,
+ art::mirror::Class* input,
+ art::mirror::Class* output)
+ REQUIRES(art::Locks::mutator_lock_) {
+ class LocalUpdate {
+ public:
+ LocalUpdate(const art::mirror::Class* root_input, art::mirror::Class* root_output)
+ : input_(root_input), output_(root_output) {}
+
+ static void Callback(art::Thread* t, void* arg) REQUIRES(art::Locks::mutator_lock_) {
+ LocalUpdate* local = reinterpret_cast<LocalUpdate*>(arg);
+
+ // Fix up the local table with a root visitor.
+ RootUpdater local_update(local->input_, local->output_);
+ t->GetJniEnv()->locals.VisitRoots(
+ &local_update, art::RootInfo(art::kRootJNILocal, t->GetThreadId()));
+ }
+
+ private:
+ const art::mirror::Class* input_;
+ art::mirror::Class* output_;
+ };
+ LocalUpdate local_upd(input, output);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::Runtime::Current()->GetThreadList()->ForEach(LocalUpdate::Callback, &local_upd);
+ }
+
// A set of all the temp classes we have handed out. We have to fix up references to these.
// For simplicity, we store the temp classes as JNI global references in a vector. Normally a
// Prepare event will closely follow, so the vector should be small.
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index f0c0dbc..5e6a23f 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -38,12 +38,17 @@
#include "art_jvmti.h"
#include "base/array_slice.h"
#include "base/logging.h"
+#include "debugger.h"
#include "dex_file.h"
#include "dex_file_types.h"
#include "events-inl.h"
#include "gc/allocation_listener.h"
#include "gc/heap.h"
#include "instrumentation.h"
+#include "jdwp/jdwp.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/object_registry.h"
#include "jit/jit.h"
#include "jit/jit_code_cache.h"
#include "jni_env_ext-inl.h"
@@ -452,6 +457,11 @@
CallbackCtx ctx(linker->GetAllocatorForClassLoader(art_klass->GetClassLoader()));
// Add all the declared methods to the map
for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+ // TODO It should be possible to simply filter out some methods where they cannot really become
+ // obsolete, such as native methods and keep their original (possibly optimized)
+ // implementations. We don't do this, however, since we would need to mark these functions
+ // (still in the classes declared_methods array) as obsolete so we will find the correct dex
+ // file to get meta-data from (for example about stack-frame size).
ctx.obsolete_methods.insert(&m);
// TODO Allow this or check in IsModifiableClass.
DCHECK(!m.IsIntrinsic());
@@ -966,6 +976,23 @@
return true;
}
+void Redefiner::ClassRedefinition::UnregisterBreakpoints() {
+ DCHECK(art::Dbg::IsDebuggerActive());
+ art::JDWP::JdwpState* state = art::Dbg::GetJdwpState();
+ if (state != nullptr) {
+ state->UnregisterLocationEventsOnClass(GetMirrorClass());
+ }
+}
+
+void Redefiner::UnregisterAllBreakpoints() {
+ if (LIKELY(!art::Dbg::IsDebuggerActive())) {
+ return;
+ }
+ for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+ redef.UnregisterBreakpoints();
+ }
+}
+
bool Redefiner::CheckAllRedefinitionAreValid() {
for (Redefiner::ClassRedefinition& redef : redefinitions_) {
if (!redef.CheckRedefinitionIsValid()) {
@@ -1044,6 +1071,7 @@
// cleaned up by the GC eventually.
return result_;
}
+ // At this point we can no longer fail without corrupting the runtime state.
int32_t counter = 0;
for (Redefiner::ClassRedefinition& redef : redefinitions_) {
if (holder.GetSourceClassLoader(counter) == nullptr) {
@@ -1051,6 +1079,7 @@
}
counter++;
}
+ UnregisterAllBreakpoints();
// Disable GC and wait for it to be done if we are a moving GC. This is fine since we are done
// allocating so no deadlocks.
art::gc::Heap* heap = runtime_->GetHeap();
@@ -1083,9 +1112,7 @@
holder.GetOriginalDexFileBytes(counter));
counter++;
}
- // TODO Verify the new Class.
// TODO Shrink the obsolete method maps if possible?
- // TODO find appropriate class loader.
// TODO Put this into a scoped thing.
runtime_->GetThreadList()->ResumeAll();
// Get back shared mutator lock as expected for return.
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h
index 421d22e..c441377 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/runtime/openjdkjvmti/ti_redefine.h
@@ -207,6 +207,8 @@
void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void UnregisterBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
private:
Redefiner* driver_;
jclass klass_;
@@ -250,6 +252,7 @@
bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder)
REQUIRES_SHARED(art::Locks::mutator_lock_);
void ReleaseAllDexFiles() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void UnregisterAllBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
void RecordFailure(jvmtiError result, const std::string& class_sig, const std::string& error_msg);
void RecordFailure(jvmtiError result, const std::string& error_msg) {
diff --git a/runtime/stack.cc b/runtime/stack.cc
index d7ba1d7..96fc664 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -874,9 +874,11 @@
CHECK_EQ(GetMethod(), callee) << "Expected: " << ArtMethod::PrettyMethod(callee)
<< " Found: " << ArtMethod::PrettyMethod(GetMethod());
} else {
- CHECK_EQ(instrumentation_frame.method_, GetMethod())
+ // Instrumentation generally doesn't distinguish between a method's obsolete and
+ // non-obsolete version.
+ CHECK_EQ(instrumentation_frame.method_, GetMethod()->GetNonObsoleteMethod())
<< "Expected: " << ArtMethod::PrettyMethod(instrumentation_frame.method_)
- << " Found: " << ArtMethod::PrettyMethod(GetMethod());
+ << " Found: " << ArtMethod::PrettyMethod(GetMethod()->GetNonObsoleteMethod());
}
if (num_frames_ != 0) {
// Check agreement of frame Ids only if num_frames_ is computed to avoid infinite
@@ -903,7 +905,7 @@
<< " native=" << method->IsNative()
<< std::noboolalpha
<< " entrypoints=" << method->GetEntryPointFromQuickCompiledCode()
- << "," << method->GetEntryPointFromJni()
+ << "," << (method->IsNative() ? method->GetEntryPointFromJni() : nullptr)
<< " next=" << *cur_quick_frame_;
}
diff --git a/runtime/zip_archive.cc b/runtime/zip_archive.cc
index cd79bb6..416873f 100644
--- a/runtime/zip_archive.cc
+++ b/runtime/zip_archive.cc
@@ -23,10 +23,16 @@
#include <unistd.h>
#include <vector>
+#include "android-base/stringprintf.h"
#include "base/unix_file/fd_file.h"
namespace art {
+// Log file contents and mmap info when mapping entries directly.
+static constexpr const bool kDebugZipMapDirectly = false;
+
+using android::base::StringPrintf;
+
uint32_t ZipEntry::GetUncompressedLength() {
return zip_entry_->uncompressed_length;
}
@@ -35,6 +41,15 @@
return zip_entry_->crc32;
}
+bool ZipEntry::IsUncompressed() {
+ return zip_entry_->method == kCompressStored;
+}
+
+bool ZipEntry::IsAlignedTo(size_t alignment) {
+ DCHECK(IsPowerOfTwo(alignment)) << alignment;
+ return IsAlignedParam(zip_entry_->offset, static_cast<int>(alignment));
+}
+
ZipEntry::~ZipEntry() {
delete zip_entry_;
}
@@ -73,6 +88,102 @@
return map.release();
}
+MemMap* ZipEntry::MapDirectlyFromFile(const char* zip_filename, std::string* error_msg) {
+ const int zip_fd = GetFileDescriptor(handle_);
+ const char* entry_filename = entry_name_.c_str();
+
+ // Should not happen since we don't have a memory ZipArchive constructor.
+ // However the underlying ZipArchive isn't required to have an FD,
+ // so check to be sure.
+ CHECK_GE(zip_fd, 0) <<
+ StringPrintf("Cannot map '%s' (in zip '%s') directly because the zip archive "
+ "is not file backed.",
+ entry_filename,
+ zip_filename);
+
+ if (!IsUncompressed()) {
+ *error_msg = StringPrintf("Cannot map '%s' (in zip '%s') directly because it is compressed.",
+ entry_filename,
+ zip_filename);
+ return nullptr;
+ } else if (zip_entry_->uncompressed_length != zip_entry_->compressed_length) {
+ *error_msg = StringPrintf("Cannot map '%s' (in zip '%s') directly because "
+ "entry has bad size (%u != %u).",
+ entry_filename,
+ zip_filename,
+ zip_entry_->uncompressed_length,
+ zip_entry_->compressed_length);
+ return nullptr;
+ }
+
+ std::string name(entry_filename);
+ name += " mapped directly in memory from ";
+ name += zip_filename;
+
+ const off_t offset = zip_entry_->offset;
+
+ if (kDebugZipMapDirectly) {
+ LOG(INFO) << "zip_archive: " << "make mmap of " << name << " @ offset = " << offset;
+ }
+
+ std::unique_ptr<MemMap> map(
+ MemMap::MapFileAtAddress(nullptr, // Expected pointer address
+ GetUncompressedLength(), // Byte count
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE,
+ zip_fd,
+ offset,
+ false, // Don't restrict allocation to lower4GB
+ false, // Doesn't overlap existing map (reuse=false)
+ name.c_str(),
+ /*out*/error_msg));
+
+ if (map == nullptr) {
+ DCHECK(!error_msg->empty());
+ }
+
+ if (kDebugZipMapDirectly) {
+ // Dump contents of file, same format as using this shell command:
+ // $> od -j <offset> -t x1 <zip_filename>
+ static constexpr const int kMaxDumpChars = 15;
+ lseek(zip_fd, 0, SEEK_SET);
+
+ int count = offset + kMaxDumpChars;
+
+ std::string tmp;
+ char buf;
+
+ // Dump file contents.
+ int i = 0;
+ while (read(zip_fd, &buf, 1) > 0 && i < count) {
+ tmp += StringPrintf("%3d ", (unsigned int)buf);
+ ++i;
+ }
+
+ LOG(INFO) << "map_fd raw bytes starting at 0";
+ LOG(INFO) << "" << tmp;
+ LOG(INFO) << "---------------------------";
+
+ // Dump map contents.
+ if (map != nullptr) {
+ tmp = "";
+
+ count = kMaxDumpChars;
+
+ uint8_t* begin = map->Begin();
+ for (i = 0; i < count; ++i) {
+ tmp += StringPrintf("%3d ", (unsigned int)begin[i]);
+ }
+
+ LOG(INFO) << "map address " << StringPrintf("%p", begin);
+ LOG(INFO) << "map first " << kMaxDumpChars << " chars:";
+ LOG(INFO) << tmp;
+ }
+ }
+
+ return map.release();
+}
+
static void SetCloseOnExec(int fd) {
// This dance is more portable than Linux's O_CLOEXEC open(2) flag.
int flags = fcntl(fd, F_GETFD);
@@ -129,7 +240,7 @@
return nullptr;
}
- return new ZipEntry(handle_, zip_entry.release());
+ return new ZipEntry(handle_, zip_entry.release(), name);
}
ZipArchive::~ZipArchive() {
diff --git a/runtime/zip_archive.h b/runtime/zip_archive.h
index 42bf55c..1858444 100644
--- a/runtime/zip_archive.h
+++ b/runtime/zip_archive.h
@@ -37,19 +37,35 @@
class ZipEntry {
public:
bool ExtractToFile(File& file, std::string* error_msg);
+ // Extract this entry to anonymous memory (R/W).
+ // Returns null on failure and sets error_msg.
MemMap* ExtractToMemMap(const char* zip_filename, const char* entry_filename,
std::string* error_msg);
+ // Create a file-backed private (clean, R/W) memory mapping to this entry.
+ // 'zip_filename' is used for diagnostics only,
+ // the original file that the ZipArchive was open with is used
+ // for the mapping.
+ //
+ // Will only succeed if the entry is stored uncompressed.
+ // Returns null on failure and sets error_msg.
+ MemMap* MapDirectlyFromFile(const char* zip_filename, /*out*/std::string* error_msg);
virtual ~ZipEntry();
uint32_t GetUncompressedLength();
uint32_t GetCrc32();
+ bool IsUncompressed();
+ bool IsAlignedTo(size_t alignment);
+
private:
ZipEntry(ZipArchiveHandle handle,
- ::ZipEntry* zip_entry) : handle_(handle), zip_entry_(zip_entry) {}
+ ::ZipEntry* zip_entry,
+ const std::string& entry_name)
+ : handle_(handle), zip_entry_(zip_entry), entry_name_(entry_name) {}
ZipArchiveHandle handle_;
::ZipEntry* const zip_entry_;
+ std::string const entry_name_;
friend class ZipArchive;
DISALLOW_COPY_AND_ASSIGN(ZipEntry);
diff --git a/test/071-dexfile-map-clean/build b/test/071-dexfile-map-clean/build
new file mode 100755
index 0000000..a171fc3
--- /dev/null
+++ b/test/071-dexfile-map-clean/build
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Any JAR files used by this test shall have their classes.dex be stored, NOT compressed.
+# This is needed for our test correctness which validates classes.dex are mapped file-backed.
+#
+# In addition, align to at least 4 bytes since that's the dex alignment requirement.
+./default-build "$@" --zip-compression-method store --zip-align 4
diff --git a/test/071-dexfile-map-clean/expected.txt b/test/071-dexfile-map-clean/expected.txt
new file mode 100644
index 0000000..af7fb28
--- /dev/null
+++ b/test/071-dexfile-map-clean/expected.txt
@@ -0,0 +1,3 @@
+Another
+Secondary dexfile mmap is clean
+Another Instance
diff --git a/test/071-dexfile-map-clean/info.txt b/test/071-dexfile-map-clean/info.txt
new file mode 100644
index 0000000..7e45808
--- /dev/null
+++ b/test/071-dexfile-map-clean/info.txt
@@ -0,0 +1,11 @@
+Exercise Dalvik-specific DEX file feature. Will not work on RI.
+
+If these conditions are met:
+* When we are loading in a secondary dex file
+* and when dex2oat is not used
+* and the dex file is stored uncompressed in a ZIP file
+
+Then check:
+* The dex file is memory-mapped file-backed as clean memory
+(i.e. there is no extraction step)
+
diff --git a/test/071-dexfile-map-clean/run b/test/071-dexfile-map-clean/run
new file mode 100755
index 0000000..b045109
--- /dev/null
+++ b/test/071-dexfile-map-clean/run
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# Run without dex2oat so that we don't create oat/vdex files
+# when trying to load the secondary dex file.
+
+# In this way, the secondary dex file will be forced to be
+# loaded directly.
+./default-run "$@" --no-dex2oat
diff --git a/test/071-dexfile-map-clean/src-ex/Another.java b/test/071-dexfile-map-clean/src-ex/Another.java
new file mode 100644
index 0000000..58464a6
--- /dev/null
+++ b/test/071-dexfile-map-clean/src-ex/Another.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Another {
+ public Another() {
+ System.out.println("Another Instance");
+ }
+}
diff --git a/test/071-dexfile-map-clean/src/Main.java b/test/071-dexfile-map-clean/src/Main.java
new file mode 100644
index 0000000..8a196dd
--- /dev/null
+++ b/test/071-dexfile-map-clean/src/Main.java
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+/**
+ * DexFile tests (Dalvik-specific).
+ */
+public class Main {
+ private static final String CLASS_PATH =
+ System.getenv("DEX_LOCATION") + "/071-dexfile-map-clean-ex.jar";
+
+ /**
+ * Prep the environment then run the test.
+ */
+ public static void main(String[] args) throws Exception {
+ // Load the dex file, this is a pre-requisite to mmap-ing it in.
+ Class<?> AnotherClass = testDexFile();
+ // Check that the memory maps are clean.
+ testDexMemoryMaps();
+
+ // Prevent garbage collector from collecting our DexFile
+ // (and unmapping too early) by using it after we finish
+ // our verification.
+ AnotherClass.newInstance();
+ }
+
+ private static boolean checkSmapsEntry(String[] smapsLines, int offset) {
+ String nameDescription = smapsLines[offset];
+ String[] split = nameDescription.split(" ");
+
+ String permissions = split[1];
+ // Mapped as read-only + anonymous.
+ if (!permissions.startsWith("r--p")) {
+ return false;
+ }
+
+ boolean validated = false;
+
+ // We have the right entry, now make sure it's valid.
+ for (int i = offset; i < smapsLines.length; ++i) {
+ String line = smapsLines[i];
+
+ if (line.startsWith("Shared_Dirty") || line.startsWith("Private_Dirty")) {
+ String lineTrimmed = line.trim();
+ String[] lineSplit = lineTrimmed.split(" +");
+
+ String sizeUsuallyInKb = lineSplit[lineSplit.length - 2];
+
+ sizeUsuallyInKb = sizeUsuallyInKb.trim();
+
+ if (!sizeUsuallyInKb.equals("0")) {
+ System.out.println(
+ "ERROR: Memory mapping for " + CLASS_PATH + " is unexpectedly dirty");
+ System.out.println(line);
+ } else {
+ validated = true;
+ }
+ }
+
+ // VmFlags marks the "end" of an smaps entry.
+ if (line.startsWith("VmFlags")) {
+ break;
+ }
+ }
+
+ if (validated) {
+ System.out.println("Secondary dexfile mmap is clean");
+ } else {
+ System.out.println("ERROR: Memory mapping is missing Shared_Dirty/Private_Dirty entries");
+ }
+
+ return true;
+ }
+
+ // This test takes relies on dex2oat being skipped.
+ // (enforced in 'run' file by using '--no-dex2oat'
+ //
+ // This could happen in a non-test situation
+ // if a secondary dex file is loaded (but not yet maintenance-mode compiled)
+ // with JIT.
+ //
+ // Or it could also happen if a secondary dex file is loaded and forced
+ // into running into the interpreter (e.g. duplicate classes).
+ //
+ // Rather than relying on those weird fallbacks,
+ // we force the runtime not to dex2oat the dex file to ensure
+ // this test is repeatable and less brittle.
+ private static void testDexMemoryMaps() throws Exception {
+ // Ensure that the secondary dex file is mapped clean (directly from JAR file).
+ String smaps = new String(Files.readAllBytes(Paths.get("/proc/self/smaps")));
+
+ String[] smapsLines = smaps.split("\n");
+ boolean found = true;
+ for (int i = 0; i < smapsLines.length; ++i) {
+ if (smapsLines[i].contains(CLASS_PATH)) {
+ if (checkSmapsEntry(smapsLines, i)) {
+ return;
+ } // else we found the wrong one, keep going.
+ }
+ }
+
+ // Error case:
+ System.out.println("Could not find " + CLASS_PATH + " RO-anonymous smaps entry");
+ System.out.println(smaps);
+ }
+
+ private static Class<?> testDexFile() throws Exception {
+ ClassLoader classLoader = Main.class.getClassLoader();
+ Class<?> DexFile = classLoader.loadClass("dalvik.system.DexFile");
+ Method DexFile_loadDex = DexFile.getMethod("loadDex",
+ String.class,
+ String.class,
+ Integer.TYPE);
+ Method DexFile_entries = DexFile.getMethod("entries");
+ Object dexFile = DexFile_loadDex.invoke(null, CLASS_PATH, null, 0);
+ Enumeration<String> e = (Enumeration<String>) DexFile_entries.invoke(dexFile);
+ while (e.hasMoreElements()) {
+ String className = e.nextElement();
+ System.out.println(className);
+ }
+
+ Method DexFile_loadClass = DexFile.getMethod("loadClass",
+ String.class,
+ ClassLoader.class);
+ Class<?> AnotherClass = (Class<?>)DexFile_loadClass.invoke(dexFile,
+ "Another", Main.class.getClassLoader());
+ return AnotherClass;
+ }
+}
diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc
index 6c12522..b727453 100644
--- a/test/912-classes/classes.cc
+++ b/test/912-classes/classes.cc
@@ -443,6 +443,9 @@
found_ = true;
stored_class_ = jni_env->NewGlobalRef(klass);
weakly_stored_class_ = jni_env->NewWeakGlobalRef(klass);
+ // The following is bad and relies on implementation details. But otherwise a test would be
+ // a lot more complicated.
+ local_stored_class_ = jni_env->NewLocalRef(klass);
}
}
@@ -455,6 +458,7 @@
CHECK(stored_class_ != nullptr);
CHECK(jni_env->IsSameObject(stored_class_, klass));
CHECK(jni_env->IsSameObject(weakly_stored_class_, klass));
+ CHECK(jni_env->IsSameObject(local_stored_class_, klass));
compared_ = true;
}
}
@@ -469,17 +473,20 @@
env->DeleteGlobalRef(stored_class_);
DCHECK(weakly_stored_class_ != nullptr);
env->DeleteWeakGlobalRef(weakly_stored_class_);
+ // Do not attempt to delete the local ref. It will be out of date by now.
}
}
private:
static jobject stored_class_;
static jweak weakly_stored_class_;
+ static jobject local_stored_class_;
static bool found_;
static bool compared_;
};
jobject ClassLoadPrepareEquality::stored_class_ = nullptr;
jweak ClassLoadPrepareEquality::weakly_stored_class_ = nullptr;
+jobject ClassLoadPrepareEquality::local_stored_class_ = nullptr;
bool ClassLoadPrepareEquality::found_ = false;
bool ClassLoadPrepareEquality::compared_ = false;
diff --git a/test/945-obsolete-native/build b/test/945-obsolete-native/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/945-obsolete-native/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-build "$@" --experimental agents
diff --git a/test/945-obsolete-native/expected.txt b/test/945-obsolete-native/expected.txt
new file mode 100644
index 0000000..83efda1
--- /dev/null
+++ b/test/945-obsolete-native/expected.txt
@@ -0,0 +1,9 @@
+hello
+Not doing anything here
+goodbye
+hello
+transforming calling function
+goodbye
+Hello - Transformed
+Not doing anything here
+Goodbye - Transformed
diff --git a/test/945-obsolete-native/info.txt b/test/945-obsolete-native/info.txt
new file mode 100644
index 0000000..c8b892c
--- /dev/null
+++ b/test/945-obsolete-native/info.txt
@@ -0,0 +1 @@
+Tests basic obsolete method support
diff --git a/test/945-obsolete-native/obsolete_native.cc b/test/945-obsolete-native/obsolete_native.cc
new file mode 100644
index 0000000..061e7af
--- /dev/null
+++ b/test/945-obsolete-native/obsolete_native.cc
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#include <inttypes.h>
+#include <memory>
+#include <stdio.h>
+
+#include "android-base/stringprintf.h"
+
+#include "android-base/stringprintf.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "jni.h"
+#include "openjdkjvmti/jvmti.h"
+#include "ScopedLocalRef.h"
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test945ObsoleteNative {
+
+extern "C" JNIEXPORT void JNICALL Java_Main_bindTest945ObsoleteNative(
+ JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+ BindFunctions(jvmti_env, env, "Transform");
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Transform_doExecute(JNIEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jobject runnable) {
+ jclass runnable_klass = env->FindClass("java/lang/Runnable");
+ DCHECK(runnable_klass != nullptr);
+ jmethodID run_method = env->GetMethodID(runnable_klass, "run", "()V");
+ env->CallVoidMethod(runnable, run_method);
+}
+
+
+} // namespace Test945ObsoleteNative
+} // namespace art
diff --git a/test/945-obsolete-native/run b/test/945-obsolete-native/run
new file mode 100755
index 0000000..c6e62ae
--- /dev/null
+++ b/test/945-obsolete-native/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-run "$@" --jvmti
diff --git a/test/945-obsolete-native/src/Main.java b/test/945-obsolete-native/src/Main.java
new file mode 100644
index 0000000..5e2154e
--- /dev/null
+++ b/test/945-obsolete-native/src/Main.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+import java.util.Base64;
+
+public class Main {
+ // class Transform {
+ // public void sayHi(Runnable r) {
+ // System.out.println("Hello - Transformed");
+ // doExecute(r);
+ // System.out.println("Goodbye - Transformed");
+ // }
+ //
+ // private static native void doExecute(Runnable r);
+ // }
+ private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAIgoACAASCQATABQIABUKABYAFwoABwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" +
+ "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" +
+ "KVYBAAlkb0V4ZWN1dGUBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAAkACgcAHAwAHQAe" +
+ "AQATSGVsbG8gLSBUcmFuc2Zvcm1lZAcAHwwAIAAhDAAPAA4BABVHb29kYnllIC0gVHJhbnNmb3Jt" +
+ "ZWQBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291" +
+ "dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxu" +
+ "AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABwAIAAAAAAADAAAACQAKAAEACwAAAB0AAQABAAAA" +
+ "BSq3AAGxAAAAAQAMAAAABgABAAAAEQABAA0ADgABAAsAAAA5AAIAAgAAABWyAAISA7YABCu4AAWy" +
+ "AAISBrYABLEAAAABAAwAAAASAAQAAAATAAgAFAAMABUAFAAWAQoADwAOAAAAAQAQAAAAAgAR");
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQB1fZcJR/opPuXacK8mIla5shH0LSg72qJYAwAAcAAAAHhWNBIAAAAAAAAAALgCAAAR" +
+ "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAUAgAARAEAAKIB" +
+ "AACqAQAAwQEAANYBAADjAQAA+gEAAA4CAAAkAgAAOAIAAEwCAABcAgAAXwIAAGMCAABuAgAAggIA" +
+ "AIcCAACQAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" +
+ "lAEAAAsAAAAGAAAAnAEAAAUAAQAOAAAAAAAAAAAAAAAAAAEADAAAAAAAAQAQAAAAAQACAA8AAAAC" +
+ "AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAKUCAAAAAAAAAQABAAEAAACXAgAABAAAAHAQ" +
+ "BAAAAA4ABAACAAIAAACcAgAAFAAAAGIAAAAbAQIAAABuIAMAEABxEAEAAwBiAAAAGwEBAAAAbiAD" +
+ "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AFUdvb2RieWUgLSBUcmFuc2Zvcm1lZAATSGVsbG8g" +
+ "LSBUcmFuc2Zvcm1lZAALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEv" +
+ "bGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJM" +
+ "amF2YS9sYW5nL1N5c3RlbTsADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAAJZG9FeGVjdXRlABJlbWl0" +
+ "dGVyOiBqYWNrLTQuMjUAA291dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAQAHDoc8hwAAAAIBAICA" +
+ "BMQCAYoCAAIB3AIADQAAAAAAAAABAAAAAAAAAAEAAAARAAAAcAAAAAIAAAAHAAAAtAAAAAMAAAAD" +
+ "AAAA0AAAAAQAAAABAAAA9AAAAAUAAAAFAAAA/AAAAAYAAAABAAAAJAEAAAEgAAACAAAARAEAAAEQ" +
+ "AAACAAAAlAEAAAIgAAARAAAAogEAAAMgAAACAAAAlwIAAAAgAAABAAAApQIAAAAQAAABAAAAuAIA" +
+ "AA==");
+
+ public static void main(String[] args) {
+ bindTest945ObsoleteNative();
+ doTest(new Transform());
+ }
+
+ public static void doTest(Transform t) {
+ t.sayHi(() -> { System.out.println("Not doing anything here"); });
+ t.sayHi(() -> {
+ System.out.println("transforming calling function");
+ doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+ });
+ t.sayHi(() -> { System.out.println("Not doing anything here"); });
+ }
+
+ // Transforms the class
+ private static native void doCommonClassRedefinition(Class<?> target,
+ byte[] classfile,
+ byte[] dexfile);
+
+ private static native void bindTest945ObsoleteNative();
+}
diff --git a/test/945-obsolete-native/src/Transform.java b/test/945-obsolete-native/src/Transform.java
new file mode 100644
index 0000000..2b7cc1b
--- /dev/null
+++ b/test/945-obsolete-native/src/Transform.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+class Transform {
+ public void sayHi(Runnable r) {
+ System.out.println("hello");
+ doExecute(r);
+ System.out.println("goodbye");
+ }
+
+ private static native void doExecute(Runnable r);
+}
diff --git a/test/Android.bp b/test/Android.bp
index d3244a6..00c890a 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -274,6 +274,7 @@
"933-misc-events/misc_events.cc",
"936-search-onload/search_onload.cc",
"944-transform-classloaders/classloader.cc",
+ "945-obsolete-native/obsolete_native.cc",
],
shared_libs: [
"libbase",
diff --git a/test/etc/default-build b/test/etc/default-build
index e9e3886..330a32e 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -60,6 +60,12 @@
HAS_SRC_DEX2OAT_UNRESOLVED=false
fi
+# Allow overriding ZIP_COMPRESSION_METHOD with e.g. 'store'
+ZIP_COMPRESSION_METHOD="deflate"
+# Align every ZIP file made by calling $ZIPALIGN command?
+WITH_ZIP_ALIGN=false
+ZIP_ALIGN_BYTES="-1"
+
DX_FLAGS=""
SKIP_DX_MERGER="false"
EXPERIMENTAL=""
@@ -118,6 +124,17 @@
DEFAULT_EXPERIMENT=""
EXPERIMENTAL="${EXPERIMENTAL} $1"
shift
+ elif [ "x$1" = "x--zip-compression-method" ]; then
+ # Allow using different zip compression method, e.g. 'store'
+ shift
+ ZIP_COMPRESSION_METHOD="$1"
+ shift
+ elif [ "x$1" = "x--zip-align" ]; then
+ # Align ZIP entries to some # of bytes.
+ shift
+ WITH_ZIP_ALIGN=true
+ ZIP_ALIGN_BYTES="$1"
+ shift
elif expr "x$1" : "x--" >/dev/null 2>&1; then
echo "unknown $0 option: $1" 1>&2
exit 1
@@ -146,6 +163,26 @@
JAVAC_ARGS="${JAVAC_ARGS} ${JAVAC_EXPERIMENTAL_ARGS[${experiment}]}"
done
+#########################################
+
+# Catch all commands to 'ZIP' and prepend extra flags.
+# Optionally, zipalign results to some alignment.
+function zip() {
+ local zip_target="$1"
+ local entry_src="$2"
+ shift 2
+
+ command zip --compression-method "$ZIP_COMPRESSION_METHOD" "$zip_target" "$entry_src" "$@"
+
+ if "$WITH_ZIP_ALIGN"; then
+ # zipalign does not operate in-place, so write results to a temp file.
+ local tmp_file="$(mktemp)"
+ "$ZIPALIGN" -f "$ZIP_ALIGN_BYTES" "$zip_target" "$tmp_file"
+ # replace original zip target with our temp file.
+ mv "$tmp_file" "$zip_target"
+ fi
+}
+
if [ -e classes.dex ]; then
zip $TEST_NAME.jar classes.dex
exit 0
diff --git a/test/run-test b/test/run-test
index 27c700e..4936039 100755
--- a/test/run-test
+++ b/test/run-test
@@ -90,6 +90,22 @@
export JACK="$JACK -g -cp $JACK_CLASSPATH"
+# Zipalign is not on the PATH in some configs, auto-detect it.
+if [ -z "$ZIPALIGN" ]; then
+ if which zipalign >/dev/null; then
+ ZIPALIGN="zipalign";
+ else
+ # TODO: Add a dependency for zipalign in Android.run-test.mk
+ # once it doesn't depend on libandroidfw (b/35246701)
+ case "$OSTYPE" in
+ darwin*) ZIPALIGN="$ANDROID_BUILD_TOP/prebuilts/sdk/tools/darwin/bin/zipalign" ;;
+ linux*) ZIPALIGN="$ANDROID_BUILD_TOP/prebuilts/sdk/tools/linux/bin/zipalign" ;;
+ *) echo "Can't find zipalign: unknown: $OSTYPE" >&2;;
+ esac
+ fi
+fi
+export ZIPALIGN
+
info="info.txt"
build="build"
run="run"
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index c5a9356..351857d 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -122,6 +122,7 @@
{ "942-private-recursive", common_redefine::OnLoad, nullptr },
{ "943-private-recursive-jit", common_redefine::OnLoad, nullptr },
{ "944-transform-classloaders", common_redefine::OnLoad, nullptr },
+ { "945-obsolete-native", common_redefine::OnLoad, nullptr },
};
static AgentLib* FindAgent(char* name) {