Merge "Layout code items of post startup methods"
diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk
index 4466118..f4f8d49 100644
--- a/build/Android.common_path.mk
+++ b/build/Android.common_path.mk
@@ -74,9 +74,6 @@
TARGET_CORE_IMG_LOCATION := $(ART_TARGET_TEST_OUT)/core.art
# Jar files for core.art.
-TARGET_CORE_JARS := core-oj core-libart conscrypt okhttp bouncycastle apache-xml
-HOST_CORE_JARS := $(addsuffix -hostdex,$(TARGET_CORE_JARS))
-
HOST_CORE_DEX_LOCATIONS := $(foreach jar,$(HOST_CORE_JARS), $(HOST_OUT_JAVA_LIBRARIES)/$(jar).jar)
ifeq ($(ART_TEST_ANDROID_ROOT),)
TARGET_CORE_DEX_LOCATIONS := $(foreach jar,$(TARGET_CORE_JARS),/$(DEXPREOPT_BOOT_JAR_DIR)/$(jar).jar)
diff --git a/dexlayout/dex_ir.cc b/dexlayout/dex_ir.cc
index 62ee445..a200d8d 100644
--- a/dexlayout/dex_ir.cc
+++ b/dexlayout/dex_ir.cc
@@ -690,10 +690,10 @@
}
MethodItem* Collections::GenerateMethodItem(const DexFile& dex_file, ClassDataItemIterator& cdii) {
- MethodId* method_item = GetMethodId(cdii.GetMemberIndex());
+ MethodId* method_id = GetMethodId(cdii.GetMemberIndex());
uint32_t access_flags = cdii.GetRawMemberAccessFlags();
const DexFile::CodeItem* disk_code_item = cdii.GetMethodCodeItem();
- CodeItem* code_item = code_items_.GetExistingObject(cdii.GetMethodCodeItemOffset());;
+ CodeItem* code_item = code_items_.GetExistingObject(cdii.GetMethodCodeItemOffset());
DebugInfoItem* debug_info = nullptr;
if (disk_code_item != nullptr) {
if (code_item == nullptr) {
@@ -707,7 +707,7 @@
disk_code_item, is_static, cdii.GetMemberIndex(), GetLocalsCb, debug_info);
dex_file.DecodeDebugPositionInfo(disk_code_item, GetPositionsCb, debug_info);
}
- return new MethodItem(access_flags, method_item, code_item);
+ return new MethodItem(access_flags, method_id, code_item);
}
ClassData* Collections::CreateClassData(
@@ -719,14 +719,14 @@
ClassDataItemIterator cdii(dex_file, encoded_data);
// Static fields.
FieldItemVector* static_fields = new FieldItemVector();
- for (uint32_t i = 0; cdii.HasNextStaticField(); i++, cdii.Next()) {
+ for (; cdii.HasNextStaticField(); cdii.Next()) {
FieldId* field_item = GetFieldId(cdii.GetMemberIndex());
uint32_t access_flags = cdii.GetRawMemberAccessFlags();
static_fields->push_back(std::unique_ptr<FieldItem>(new FieldItem(access_flags, field_item)));
}
// Instance fields.
FieldItemVector* instance_fields = new FieldItemVector();
- for (uint32_t i = 0; cdii.HasNextInstanceField(); i++, cdii.Next()) {
+ for (; cdii.HasNextInstanceField(); cdii.Next()) {
FieldId* field_item = GetFieldId(cdii.GetMemberIndex());
uint32_t access_flags = cdii.GetRawMemberAccessFlags();
instance_fields->push_back(
@@ -734,15 +734,13 @@
}
// Direct methods.
MethodItemVector* direct_methods = new MethodItemVector();
- for (uint32_t i = 0; cdii.HasNextDirectMethod(); i++, cdii.Next()) {
- direct_methods->push_back(
- std::unique_ptr<MethodItem>(GenerateMethodItem(dex_file, cdii)));
+ for (; cdii.HasNextDirectMethod(); cdii.Next()) {
+ direct_methods->push_back(std::unique_ptr<MethodItem>(GenerateMethodItem(dex_file, cdii)));
}
// Virtual methods.
MethodItemVector* virtual_methods = new MethodItemVector();
- for (uint32_t i = 0; cdii.HasNextVirtualMethod(); i++, cdii.Next()) {
- virtual_methods->push_back(
- std::unique_ptr<MethodItem>(GenerateMethodItem(dex_file, cdii)));
+ for (; cdii.HasNextVirtualMethod(); cdii.Next()) {
+ virtual_methods->push_back(std::unique_ptr<MethodItem>(GenerateMethodItem(dex_file, cdii)));
}
class_data = new ClassData(static_fields, instance_fields, direct_methods, virtual_methods);
class_data->SetSize(cdii.EndDataPointer() - encoded_data);
diff --git a/dexlayout/dex_ir.h b/dexlayout/dex_ir.h
index 95e64bf..fe74572 100644
--- a/dexlayout/dex_ir.h
+++ b/dexlayout/dex_ir.h
@@ -215,14 +215,38 @@
const DexFile& dex_file, const DexFile::CodeItem& disk_code_item, uint32_t offset);
ClassData* CreateClassData(const DexFile& dex_file, const uint8_t* encoded_data, uint32_t offset);
- StringId* GetStringId(uint32_t index) { return StringIds()[index].get(); }
- TypeId* GetTypeId(uint32_t index) { return TypeIds()[index].get(); }
- ProtoId* GetProtoId(uint32_t index) { return ProtoIds()[index].get(); }
- FieldId* GetFieldId(uint32_t index) { return FieldIds()[index].get(); }
- MethodId* GetMethodId(uint32_t index) { return MethodIds()[index].get(); }
- ClassDef* GetClassDef(uint32_t index) { return ClassDefs()[index].get(); }
- CallSiteId* GetCallSiteId(uint32_t index) { return CallSiteIds()[index].get(); }
- MethodHandleItem* GetMethodHandle(uint32_t index) { return MethodHandleItems()[index].get(); }
+ StringId* GetStringId(uint32_t index) {
+ CHECK_LT(index, StringIdsSize());
+ return StringIds()[index].get();
+ }
+ TypeId* GetTypeId(uint32_t index) {
+ CHECK_LT(index, TypeIdsSize());
+ return TypeIds()[index].get();
+ }
+ ProtoId* GetProtoId(uint32_t index) {
+ CHECK_LT(index, ProtoIdsSize());
+ return ProtoIds()[index].get();
+ }
+ FieldId* GetFieldId(uint32_t index) {
+ CHECK_LT(index, FieldIdsSize());
+ return FieldIds()[index].get();
+ }
+ MethodId* GetMethodId(uint32_t index) {
+ CHECK_LT(index, MethodIdsSize());
+ return MethodIds()[index].get();
+ }
+ ClassDef* GetClassDef(uint32_t index) {
+ CHECK_LT(index, ClassDefsSize());
+ return ClassDefs()[index].get();
+ }
+ CallSiteId* GetCallSiteId(uint32_t index) {
+ CHECK_LT(index, CallSiteIdsSize());
+ return CallSiteIds()[index].get();
+ }
+ MethodHandleItem* GetMethodHandle(uint32_t index) {
+ CHECK_LT(index, MethodHandleItemsSize());
+ return MethodHandleItems()[index].get();
+ }
StringId* GetStringIdOrNullPtr(uint32_t index) {
return index == DexFile::kDexNoIndex ? nullptr : GetStringId(index);
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 7e70b75..12bdb32 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -2927,7 +2927,8 @@
void Dbg::PostFieldAccessEvent(ArtMethod* m, int dex_pc,
mirror::Object* this_object, ArtField* f) {
- if (!IsDebuggerActive()) {
+ // TODO We should send events for native methods.
+ if (!IsDebuggerActive() || m->IsNative()) {
return;
}
DCHECK(m != nullptr);
@@ -2941,7 +2942,8 @@
void Dbg::PostFieldModificationEvent(ArtMethod* m, int dex_pc,
mirror::Object* this_object, ArtField* f,
const JValue* field_value) {
- if (!IsDebuggerActive()) {
+ // TODO We should send events for native methods.
+ if (!IsDebuggerActive() || m->IsNative()) {
return;
}
DCHECK(m != nullptr);
diff --git a/runtime/dex_file_tracking_registrar.cc b/runtime/dex_file_tracking_registrar.cc
index f41a50b..848e2f3 100644
--- a/runtime/dex_file_tracking_registrar.cc
+++ b/runtime/dex_file_tracking_registrar.cc
@@ -69,7 +69,10 @@
// Intended for local changes only.
void DexFileTrackingRegistrar::SetDexSections() {
- if (kDexFileAccessTracking || dex_file_ != nullptr) {
+ if (kDexFileAccessTracking && dex_file_ != nullptr) {
+ // Logs the Dex File's location and starting address if tracking is enabled
+ LOG(ERROR) << "RegisterDexFile: " << dex_file_->GetLocation() + " @ " << std::hex
+ << reinterpret_cast<uintptr_t>(dex_file_->Begin());
switch (kCurrentTrackingSystem) {
case kWholeDexTracking:
SetDexFileRegistration(true);
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 6be0953..0aabbea 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -40,6 +40,7 @@
#include "interpreter/interpreter.h"
#include "jni_env_ext.h"
#include "java_vm_ext.h"
+#include "jvalue-inl.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
#include "mirror/field-inl.h"
@@ -64,6 +65,84 @@
// things not rendering correctly. E.g. b/16858794
static constexpr bool kWarnJniAbort = false;
+// Helpers to call instrumentation functions for fields. These take jobjects so we don't need to set
+// up handles for the rare case where these actually do something. Once these functions return it is
+// possible there will be a pending exception if the instrumentation happens to throw one.
+static void NotifySetObjectField(ArtField* field, jobject obj, jobject jval)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK_EQ(field->GetTypeAsPrimitiveType(), Primitive::kPrimNot);
+ instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+ if (UNLIKELY(instrumentation->HasFieldWriteListeners())) {
+ Thread* self = Thread::Current();
+ ArtMethod* cur_method = self->GetCurrentMethod(/*dex_pc*/ nullptr,
+ /*check_suspended*/ true,
+ /*abort_on_error*/ false);
+
+ if (cur_method == nullptr) {
+ // Set/Get Fields can be issued without a method during runtime startup/teardown. Ignore all
+ // of these changes.
+ return;
+ }
+ DCHECK(cur_method->IsNative());
+ JValue val;
+ val.SetL(self->DecodeJObject(jval));
+ instrumentation->FieldWriteEvent(self,
+ self->DecodeJObject(obj).Ptr(),
+ cur_method,
+ 0, // dex_pc is always 0 since this is a native method.
+ field,
+ val);
+ }
+}
+
+static void NotifySetPrimitiveField(ArtField* field, jobject obj, JValue val)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK_NE(field->GetTypeAsPrimitiveType(), Primitive::kPrimNot);
+ instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+ if (UNLIKELY(instrumentation->HasFieldWriteListeners())) {
+ Thread* self = Thread::Current();
+ ArtMethod* cur_method = self->GetCurrentMethod(/*dex_pc*/ nullptr,
+ /*check_suspended*/ true,
+ /*abort_on_error*/ false);
+
+ if (cur_method == nullptr) {
+ // Set/Get Fields can be issued without a method during runtime startup/teardown. Ignore all
+ // of these changes.
+ return;
+ }
+ DCHECK(cur_method->IsNative());
+ instrumentation->FieldWriteEvent(self,
+ self->DecodeJObject(obj).Ptr(),
+ cur_method,
+ 0, // dex_pc is always 0 since this is a native method.
+ field,
+ val);
+ }
+}
+
+static void NotifyGetField(ArtField* field, jobject obj)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+ if (UNLIKELY(instrumentation->HasFieldReadListeners())) {
+ Thread* self = Thread::Current();
+ ArtMethod* cur_method = self->GetCurrentMethod(/*dex_pc*/ nullptr,
+ /*check_suspended*/ true,
+ /*abort_on_error*/ false);
+
+ if (cur_method == nullptr) {
+ // Set/Get Fields can be issued without a method during runtime startup/teardown. Ignore all
+ // of these changes.
+ return;
+ }
+ DCHECK(cur_method->IsNative());
+ instrumentation->FieldReadEvent(self,
+ self->DecodeJObject(obj).Ptr(),
+ cur_method,
+ 0, // dex_pc is always 0 since this is a native method.
+ field);
+ }
+}
+
// Section 12.3.2 of the JNI spec describes JNI class descriptors. They're
// separated with slashes but aren't wrapped with "L;" like regular descriptors
// (i.e. "a/b/C" rather than "La/b/C;"). Arrays of reference types are an
@@ -1235,8 +1314,9 @@
CHECK_NON_NULL_ARGUMENT(obj);
CHECK_NON_NULL_ARGUMENT(fid);
ScopedObjectAccess soa(env);
- ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(obj);
ArtField* f = jni::DecodeArtField(fid);
+ NotifyGetField(f, obj);
+ ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(obj);
return soa.AddLocalReference<jobject>(f->GetObject(o));
}
@@ -1244,6 +1324,7 @@
CHECK_NON_NULL_ARGUMENT(fid);
ScopedObjectAccess soa(env);
ArtField* f = jni::DecodeArtField(fid);
+ NotifyGetField(f, nullptr);
return soa.AddLocalReference<jobject>(f->GetObject(f->GetDeclaringClass()));
}
@@ -1251,17 +1332,19 @@
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(java_object);
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(fid);
ScopedObjectAccess soa(env);
+ ArtField* f = jni::DecodeArtField(fid);
+ NotifySetObjectField(f, java_object, java_value);
ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
ObjPtr<mirror::Object> v = soa.Decode<mirror::Object>(java_value);
- ArtField* f = jni::DecodeArtField(fid);
f->SetObject<false>(o, v);
}
static void SetStaticObjectField(JNIEnv* env, jclass, jfieldID fid, jobject java_value) {
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(fid);
ScopedObjectAccess soa(env);
- ObjPtr<mirror::Object> v = soa.Decode<mirror::Object>(java_value);
ArtField* f = jni::DecodeArtField(fid);
+ NotifySetObjectField(f, nullptr, java_value);
+ ObjPtr<mirror::Object> v = soa.Decode<mirror::Object>(java_value);
f->SetObject<false>(f->GetDeclaringClass(), v);
}
@@ -1269,28 +1352,32 @@
CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(instance); \
CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(fid); \
ScopedObjectAccess soa(env); \
- ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(instance); \
ArtField* f = jni::DecodeArtField(fid); \
+ NotifyGetField(f, instance); \
+ ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(instance); \
return f->Get ##fn (o)
#define GET_STATIC_PRIMITIVE_FIELD(fn) \
CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(fid); \
ScopedObjectAccess soa(env); \
ArtField* f = jni::DecodeArtField(fid); \
+ NotifyGetField(f, nullptr); \
return f->Get ##fn (f->GetDeclaringClass())
#define SET_PRIMITIVE_FIELD(fn, instance, value) \
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(instance); \
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(fid); \
ScopedObjectAccess soa(env); \
- ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(instance); \
ArtField* f = jni::DecodeArtField(fid); \
+ NotifySetPrimitiveField(f, instance, JValue::FromPrimitive<decltype(value)>(value)); \
+ ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(instance); \
f->Set ##fn <false>(o, value)
#define SET_STATIC_PRIMITIVE_FIELD(fn, value) \
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(fid); \
ScopedObjectAccess soa(env); \
ArtField* f = jni::DecodeArtField(fid); \
+ NotifySetPrimitiveField(f, nullptr, JValue::FromPrimitive<decltype(value)>(value)); \
f->Set ##fn <false>(f->GetDeclaringClass(), value)
static jboolean GetBooleanField(JNIEnv* env, jobject obj, jfieldID fid) {
diff --git a/runtime/jvalue-inl.h b/runtime/jvalue-inl.h
index b33686c..25e34b2 100644
--- a/runtime/jvalue-inl.h
+++ b/runtime/jvalue-inl.h
@@ -27,6 +27,24 @@
l = new_l.Ptr();
}
+#define DEFINE_FROM(type, chr) \
+ template <> inline JValue JValue::FromPrimitive(type v) { \
+ JValue res; \
+ res.Set ## chr(v); \
+ return res; \
+ }
+
+DEFINE_FROM(uint8_t, Z);
+DEFINE_FROM(int8_t, B);
+DEFINE_FROM(uint16_t, C);
+DEFINE_FROM(int16_t, S);
+DEFINE_FROM(int32_t, I);
+DEFINE_FROM(int64_t, J);
+DEFINE_FROM(float, F);
+DEFINE_FROM(double, D);
+
+#undef DEFINE_FROM
+
} // namespace art
#endif // ART_RUNTIME_JVALUE_INL_H_
diff --git a/runtime/jvalue.h b/runtime/jvalue.h
index f61a07c..266abcf 100644
--- a/runtime/jvalue.h
+++ b/runtime/jvalue.h
@@ -33,6 +33,8 @@
// We default initialize JValue instances to all-zeros.
JValue() : j(0) {}
+ template<typename T> static JValue FromPrimitive(T v);
+
int8_t GetB() const { return b; }
void SetB(int8_t new_b) {
j = ((static_cast<int64_t>(new_b) << 56) >> 56); // Sign-extend to 64 bits.
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index cd078b6..3321814 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -313,8 +313,10 @@
art::Thread* thread = art::Thread::Current();
ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+ art::ObjPtr<art::mirror::Object> peer(thread->GetPeer());
ScopedLocalRef<jthread> thread_jni(
- thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer()));
+ thread->GetJniEnv(),
+ peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer));
{
art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>(
@@ -341,8 +343,10 @@
}
ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+ art::ObjPtr<art::mirror::Object> peer(thread->GetPeer());
ScopedLocalRef<jthread> thread_jni(
- thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer()));
+ thread->GetJniEnv(),
+ peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer));
art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
event_handler->DispatchEvent<ArtJvmtiEvent::kClassPrepare>(
thread,
diff --git a/test/044-proxy/check b/test/044-proxy/check
new file mode 100755
index 0000000..0cb2c32
--- /dev/null
+++ b/test/044-proxy/check
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# 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.
+
+expected="$1"
+output="$2"
+
+# Jack inserts a synthetic default method for interface methods with covariant return types.
+# Javac does not do this, so we must fiddle with the expected.txt to get the different behavior.
+
+expected_jack_line='Invocation of public default java[.]lang[.]Object NarrowingTest\$I2[.]foo[(][)]'
+replaced_javac_line='Invocation of public abstract java.lang.Object NarrowingTest\$I1.foo()'
+
+expected_replaced="$expected"
+
+if [[ $USE_JACK == false ]]; then
+ expected_replaced="$output.tmp"
+ sed "s:$expected_jack_line:$replaced_javac_line:g" "$expected" > "$expected_replaced"
+fi
+
+diff --strip-trailing-cr -q "$expected_replaced" "$output" >/dev/null
diff --git a/test/104-growth-limit/src/Main.java b/test/104-growth-limit/src/Main.java
index d31cbf1..24acb91 100644
--- a/test/104-growth-limit/src/Main.java
+++ b/test/104-growth-limit/src/Main.java
@@ -21,7 +21,6 @@
public class Main {
public static void main(String[] args) throws Exception {
- int alloc1 = 1;
// Setup reflection stuff before allocating to prevent OOME caused by allocations from
// Class.forName or getDeclaredMethod.
// Reflective equivalent of: dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
@@ -29,34 +28,38 @@
final Method get_runtime = vm_runtime.getDeclaredMethod("getRuntime");
final Object runtime = get_runtime.invoke(null);
final Method clear_growth_limit = vm_runtime.getDeclaredMethod("clearGrowthLimit");
- List<byte[]> l = new ArrayList<byte[]>();
- try {
- while (true) {
- // Allocate a MB at a time
- l.add(new byte[1048576]);
- alloc1++;
- }
- } catch (OutOfMemoryError e) {
- l = null;
- }
+
+ int alloc1 = allocateTillOOME();
+
+ // Proactively clean up.
+ Runtime.getRuntime().gc();
+
// Expand the heap to the maximum size.
clear_growth_limit.invoke(runtime);
- int alloc2 = 1;
- l = new ArrayList<byte[]>();
- try {
- while (true) {
- // Allocate a MB at a time
- l.add(new byte[1048576]);
- alloc2++;
- }
- } catch (OutOfMemoryError e2) {
- l = null;
- if (alloc1 > alloc2) {
- System.out.println("ERROR: Allocated less memory after growth" +
+
+ int alloc2 = allocateTillOOME();
+
+ if (alloc1 > alloc2) {
+ System.out.println("ERROR: Allocated less memory after growth" +
"limit cleared (" + alloc1 + " MBs > " + alloc2 + " MBs");
- System.exit(1);
- }
+ } else {
+ System.out.println("Test complete");
}
- System.out.println("Test complete");
+ }
+
+ private static int allocateTillOOME() {
+ int allocations = 0;
+ List<byte[]> l = new ArrayList<byte[]>();
+ try {
+ while (true) {
+ // Allocate a MB at a time
+ l.add(new byte[1048576]);
+ allocations++;
+ }
+ } catch (OutOfMemoryError e) {
+ // Help clean up.
+ l.clear();
+ }
+ return allocations;
}
}
diff --git a/test/551-checker-shifter-operand/build b/test/551-checker-shifter-operand/build
deleted file mode 100644
index 027a0ea..0000000
--- a/test/551-checker-shifter-operand/build
+++ /dev/null
@@ -1,212 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-
-
-# This is an almost exact copy of `art/test/etc/default-build`. Only the parsing
-# of `dx` option has been overriden.
-
-# Stop if something fails.
-set -e
-
-# Set default values for directories.
-if [ -d smali ]; then
- HAS_SMALI=true
-else
- HAS_SMALI=false
-fi
-
-if [ -d src ]; then
- HAS_SRC=true
-else
- HAS_SRC=false
-fi
-
-if [ -d src2 ]; then
- HAS_SRC2=true
-else
- HAS_SRC2=false
-fi
-
-if [ -d src-multidex ]; then
- HAS_SRC_MULTIDEX=true
-else
- HAS_SRC_MULTIDEX=false
-fi
-
-if [ -d src-ex ]; then
- HAS_SRC_EX=true
-else
- HAS_SRC_EX=false
-fi
-
-DX_FLAGS=""
-SKIP_DX_MERGER="false"
-EXPERIMENTAL=""
-
-# Setup experimental flag mappings in a bash associative array.
-declare -A JACK_EXPERIMENTAL_ARGS
-JACK_EXPERIMENTAL_ARGS["default-methods"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
-JACK_EXPERIMENTAL_ARGS["lambdas"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
-
-while true; do
- if [ "x$1" = "x--dx-option" ]; then
- shift
- option="$1"
- # Make sure we run this test *with* `dx` optimizations.
- if [ "x$option" != "x--no-optimize" ]; then
- DX_FLAGS="${DX_FLAGS} $option"
- fi
- shift
- elif [ "x$1" = "x--jvm" ]; then
- shift
- elif [ "x$1" = "x--no-src" ]; then
- HAS_SRC=false
- shift
- elif [ "x$1" = "x--no-src2" ]; then
- HAS_SRC2=false
- shift
- elif [ "x$1" = "x--no-src-multidex" ]; then
- HAS_SRC_MULTIDEX=false
- shift
- elif [ "x$1" = "x--no-src-ex" ]; then
- HAS_SRC_EX=false
- shift
- elif [ "x$1" = "x--no-smali" ]; then
- HAS_SMALI=false
- shift
- elif [ "x$1" = "x--experimental" ]; then
- shift
- EXPERIMENTAL="${EXPERIMENTAL} $1"
- shift
- elif expr "x$1" : "x--" >/dev/null 2>&1; then
- echo "unknown $0 option: $1" 1>&2
- exit 1
- else
- break
- fi
-done
-
-# Add args from the experimental mappings.
-for experiment in ${EXPERIMENTAL}; do
- JACK_ARGS="${JACK_ARGS} ${JACK_EXPERIMENTAL_ARGS[${experiment}]}"
-done
-
-if [ -e classes.dex ]; then
- zip $TEST_NAME.jar classes.dex
- exit 0
-fi
-
-if ! [ "${HAS_SRC}" = "true" ] && ! [ "${HAS_SRC2}" = "true" ]; then
- # No src directory? Then forget about trying to run dx.
- SKIP_DX_MERGER="true"
-fi
-
-if [ "${HAS_SRC_MULTIDEX}" = "true" ]; then
- # Jack does not support this configuration unless we specify how to partition the DEX file
- # with a .jpp file.
- USE_JACK="false"
-fi
-
-if [ ${USE_JACK} = "true" ]; then
- # Jack toolchain
- if [ "${HAS_SRC}" = "true" ]; then
- ${JACK} ${JACK_ARGS} --output-jack src.jack src
- imported_jack_files="--import src.jack"
- fi
-
- if [ "${HAS_SRC2}" = "true" ]; then
- ${JACK} ${JACK_ARGS} --output-jack src2.jack src2
- imported_jack_files="--import src2.jack ${imported_jack_files}"
- fi
-
- # Compile jack files into a DEX file. We set jack.import.type.policy=keep-first to consider
- # class definitions from src2 first.
- if [ "${HAS_SRC}" = "true" ] || [ "${HAS_SRC2}" = "true" ]; then
- ${JACK} ${JACK_ARGS} ${imported_jack_files} -D jack.import.type.policy=keep-first --output-dex .
- fi
-else
- # Legacy toolchain with javac+dx
- if [ "${HAS_SRC}" = "true" ]; then
- mkdir classes
- ${JAVAC} ${JAVAC_ARGS} -implicit:none -classpath src-multidex -d classes `find src -name '*.java'`
- fi
-
- if [ "${HAS_SRC_MULTIDEX}" = "true" ]; then
- mkdir classes2
- ${JAVAC} -implicit:none -classpath src -d classes2 `find src-multidex -name '*.java'`
- if [ ${NEED_DEX} = "true" ]; then
- ${DX} -JXmx256m --debug --dex --dump-to=classes2.lst --output=classes2.dex \
- --dump-width=1000 ${DX_FLAGS} classes2
- fi
- fi
-
- if [ "${HAS_SRC2}" = "true" ]; then
- mkdir -p classes
- ${JAVAC} ${JAVAC_ARGS} -d classes `find src2 -name '*.java'`
- fi
-
- if [ "${HAS_SRC}" = "true" ] || [ "${HAS_SRC2}" = "true" ]; then
- if [ ${NEED_DEX} = "true" -a ${SKIP_DX_MERGER} = "false" ]; then
- ${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex \
- --dump-width=1000 ${DX_FLAGS} classes
- fi
- fi
-fi
-
-if [ "${HAS_SMALI}" = "true" ]; then
- # Compile Smali classes
- ${SMALI} -JXmx512m assemble ${SMALI_ARGS} --output smali_classes.dex `find smali -name '*.smali'`
-
- # Don't bother with dexmerger if we provide our own main function in a smali file.
- if [ ${SKIP_DX_MERGER} = "false" ]; then
- ${DXMERGER} classes.dex classes.dex smali_classes.dex
- else
- mv smali_classes.dex classes.dex
- fi
-fi
-
-if [ ${HAS_SRC_EX} = "true" ]; then
- if [ ${USE_JACK} = "true" ]; then
- # Rename previous "classes.dex" so it is not overwritten.
- mv classes.dex classes-1.dex
- #TODO find another way to append src.jack to the jack classpath
- ${JACK}:src.jack ${JACK_ARGS} --output-dex . src-ex
- zip $TEST_NAME-ex.jar classes.dex
- # Restore previous "classes.dex" so it can be zipped.
- mv classes-1.dex classes.dex
- else
- mkdir classes-ex
- ${JAVAC} ${JAVAC_ARGS} -d classes-ex -cp classes `find src-ex -name '*.java'`
- if [ ${NEED_DEX} = "true" ]; then
- ${DX} -JXmx256m --debug --dex --dump-to=classes-ex.lst --output=classes-ex.dex \
- --dump-width=1000 ${DX_FLAGS} classes-ex
-
- # quick shuffle so that the stored name is "classes.dex"
- mv classes.dex classes-1.dex
- mv classes-ex.dex classes.dex
- zip $TEST_NAME-ex.jar classes.dex
- mv classes.dex classes-ex.dex
- mv classes-1.dex classes.dex
- fi
- fi
-fi
-
-# Create a single jar with two dex files for multidex.
-if [ ${HAS_SRC_MULTIDEX} = "true" ]; then
- zip $TEST_NAME.jar classes.dex classes2.dex
-elif [ ${NEED_DEX} = "true" ]; then
- zip $TEST_NAME.jar classes.dex
-fi
diff --git a/test/636-arm64-veneer-pool/build b/test/636-arm64-veneer-pool/build
index 27cc4d6..eba22fc 100755
--- a/test/636-arm64-veneer-pool/build
+++ b/test/636-arm64-veneer-pool/build
@@ -19,4 +19,9 @@
# Use javac+dx instead of jack.
export USE_JACK=false
-./default-build "$@"
+
+# Don't use desugar because the bootclasspath jars will be missing
+# on a platform build compiled with ANDROID_COMPILE_WITH_JACK=true.
+export USE_DESUGAR=false
+
+./default-build
diff --git a/test/641-checker-arraycopy/build b/test/641-checker-arraycopy/build
index 9abc618..12e4423 100644
--- a/test/641-checker-arraycopy/build
+++ b/test/641-checker-arraycopy/build
@@ -21,4 +21,8 @@
# the typed System.arraycopy versions directly.
export USE_JACK=false
-./default-build
+# Don't use desugar because the bootclasspath jars will be missing
+# on a platform build compiled with ANDROID_COMPILE_WITH_JACK=true.
+export USE_DESUGAR=false
+
+./default-build "$@"
diff --git a/test/etc/default-build b/test/etc/default-build
index 0508b85..ba413ec 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -60,6 +60,11 @@
HAS_SRC_DEX2OAT_UNRESOLVED=false
fi
+# DESUGAR=false run-test... will disable desugar.
+if [[ "$DESUGAR" == false ]]; then
+ USE_DESUGAR=false
+fi
+
# Allow overriding ZIP_COMPRESSION_METHOD with e.g. 'store'
ZIP_COMPRESSION_METHOD="deflate"
# Align every ZIP file made by calling $ZIPALIGN command?
@@ -70,6 +75,9 @@
SKIP_DX_MERGER="false"
EXPERIMENTAL=""
+BUILD_MODE="target"
+DEV_MODE="no"
+
# The key for default arguments if no experimental things are enabled.
DEFAULT_EXPERIMENT="no-experiment"
@@ -137,6 +145,15 @@
WITH_ZIP_ALIGN=true
ZIP_ALIGN_BYTES="$1"
shift
+ elif [ "x$1" = "x--host" ]; then
+ BUILD_MODE="host"
+ shift
+ elif [ "x$1" = "x--target" ]; then
+ BUILD_MODE="target"
+ shift
+ elif [ "x$1" = "x--dev" ]; then
+ DEV_MODE="yes"
+ shift
elif expr "x$1" : "x--" >/dev/null 2>&1; then
echo "unknown $0 option: $1" 1>&2
exit 1
@@ -185,6 +202,41 @@
fi
}
+function desugar() {
+ local desugar_args=--mode=host
+ if [[ $BUILD_MODE == target ]]; then
+ desugar_args=--mode=target
+ fi
+
+ if [[ $DEV_MODE == yes ]]; then
+ desugar_args="$desugar_args --show-commands"
+ fi
+
+ "$DESUGAR" --core-only $desugar_args "$@"
+}
+
+# Make a "dex" file given a directory of classes in $1.
+# Also calls desugar on the classes first to convert lambdas.
+function make_dex() {
+ local name="$1"
+
+ local dx_input
+ if [[ "$USE_DESUGAR" == "true" ]]; then
+ # Make a jar first so desugar doesn't need every .class file individually.
+ jar cf "$name.before-desugar.jar" -C "$name" .
+
+ dx_input="${name}.desugar.jar"
+
+ # Make desugared JAR.
+ desugar --input "$name.before-desugar.jar" --output "$dx_input"
+ else
+ dx_input="${name}"
+ fi
+
+ # Make dex file from desugared JAR.
+ ${DX} -JXmx256m --debug --dex --dump-to=${name}.lst --output=${name}.dex --dump-width=1000 ${DX_FLAGS} "${dx_input}"
+}
+
if [ -e classes.dex ]; then
zip $TEST_NAME.jar classes.dex
exit 0
@@ -209,9 +261,9 @@
${JACK} --import classes.jill.jar --output-dex .
else
if [ ${NEED_DEX} = "true" ]; then
- ${DX} -JXmx256m --debug --dex --dump-to=classes-ex.lst --output=classes.dex --dump-width=1000 ${DX_FLAGS} classes-ex
+ make_dex classes-ex
zip ${TEST_NAME}-ex.jar classes.dex
- ${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex --dump-width=1000 ${DX_FLAGS} classes
+ make_dex classes
fi
fi
else
@@ -254,8 +306,7 @@
mkdir classes2
${JAVAC} -implicit:none -classpath src -d classes2 `find src-multidex -name '*.java'`
if [ ${NEED_DEX} = "true" ]; then
- ${DX} -JXmx256m --debug --dex --dump-to=classes2.lst --output=classes2.dex \
- --dump-width=1000 ${DX_FLAGS} classes2
+ make_dex classes2
fi
fi
@@ -266,8 +317,7 @@
if [ "${HAS_SRC}" = "true" ] || [ "${HAS_SRC2}" = "true" ]; then
if [ ${NEED_DEX} = "true" -a ${SKIP_DX_MERGER} = "false" ]; then
- ${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex \
- --dump-width=1000 ${DX_FLAGS} classes
+ make_dex classes
fi
fi
fi
@@ -311,8 +361,7 @@
mkdir classes-ex
${JAVAC} ${JAVAC_ARGS} -d classes-ex -cp classes `find src-ex -name '*.java'`
if [ ${NEED_DEX} = "true" ]; then
- ${DX} -JXmx256m --debug --dex --dump-to=classes-ex.lst --output=classes-ex.dex \
- --dump-width=1000 ${DX_FLAGS} classes-ex
+ make_dex classes-ex
# quick shuffle so that the stored name is "classes.dex"
mv classes.dex classes-1.dex
diff --git a/test/knownfailures.json b/test/knownfailures.json
index b89d457..b7f5d4c 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -601,38 +601,28 @@
"004-ReferenceMap",
"004-StackWalk",
"048-reflect-v8",
+ "065-mismatched-implements",
"089-many-methods",
"138-duplicate-classes-check",
"146-bad-interface",
"157-void-class",
+ "529-checker-unresolved",
"563-checker-invoke-super",
"580-checker-string-fact-intrinsics",
"596-monitor-inflation",
"604-hot-static-interface",
+ "608-checker-unresolved-lse",
"612-jit-dex-cache",
"613-inlining-dex-cache",
"616-cha-interface-default",
"636-wrong-static-access",
+ "648-inline-caches-unresolved",
"909-attach-agent",
"910-methods",
"911-get-stack-trace",
"912-classes",
"913-heaps",
- "914-hello-obsolescence",
- "915-obsolete-2",
- "916-obsolete-jit",
- "919-obsolete-fields",
- "921-hello-failure",
- "926-multi-obsolescence",
- "940-recursive-obsolete",
- "941-recurive-obsolete-jit",
- "942-private-recursive",
- "943-private-recursive-jit",
- "945-obsolete-native",
- "946-obsolete-throw",
"948-change-annotations",
- "950-redefine-intrinsic",
- "951-threaded-obsolete",
"952-invoke-custom",
"953-invoke-polymorphic-compiler",
"956-methodhandles",
@@ -648,9 +638,7 @@
"966-default-conflict",
"967-default-ame",
"969-iface-super",
- "981-dedup-original-dex",
- "984-obsolete-invoke",
- "985-re-obsolete"
+ "981-dedup-original-dex"
],
"description": "The tests above fail with --build-with-javac-dx.",
"env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"},
diff --git a/test/run-test b/test/run-test
index 41a0dc2..1b6df16 100755
--- a/test/run-test
+++ b/test/run-test
@@ -46,6 +46,7 @@
export DEX_LOCATION=/data/run-test/${test_dir}
export NEED_DEX="true"
export USE_JACK="true"
+export USE_DESUGAR="true"
export SMALI_ARGS=""
# If dx was not set by the environment variable, assume it is in the path.
@@ -90,6 +91,11 @@
export JACK="$JACK -g -cp $JACK_CLASSPATH"
+# Allow changing DESUGAR script to something else, or to disable it with DESUGAR=false.
+if [ -z "$DESUGAR"]; then
+ export DESUGAR="$ANDROID_BUILD_TOP/art/tools/desugar.sh"
+fi
+
# Zipalign is not on the PATH in some configs, auto-detect it.
if [ -z "$ZIPALIGN" ]; then
if which zipalign >/dev/null; then
@@ -771,6 +777,16 @@
err_echo "ulimit file size setting failed"
fi
+if [[ "$target_mode" == "yes" ]]; then
+ build_args="$build_args --target"
+else
+ build_args="$build_args --host"
+fi
+
+if [[ "$dev_mode" == "yes" ]]; then
+ build_args="$build_args --dev"
+fi
+
good="no"
good_build="yes"
good_run="yes"
diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc
index 515a391..76f8943 100644
--- a/test/ti-stress/stress.cc
+++ b/test/ti-stress/stress.cc
@@ -338,6 +338,20 @@
LOG(INFO) << "Entering method \"" << method_info << "\". Thread is \"" << info.GetName() << "\"";
}
+void JNICALL ClassPrepareHook(jvmtiEnv* jvmtienv,
+ JNIEnv* env,
+ jthread thread,
+ jclass klass) {
+ ScopedThreadInfo info(jvmtienv, env, thread);
+ ScopedClassInfo class_info(jvmtienv, klass);
+ if (!class_info.Init()) {
+ LOG(ERROR) << "Unable to get class info!";
+ return;
+ }
+ LOG(INFO) << "Prepared class \"" << class_info.GetName() << "\". Thread is \""
+ << info.GetName() << "\"";
+}
+
// The hook we are using.
void JNICALL ClassFileLoadHookSecretNoOp(jvmtiEnv* jvmti,
JNIEnv* jni_env ATTRIBUTE_UNUSED,
@@ -487,6 +501,7 @@
cb.VMInit = PerformFinalSetupVMInit;
cb.MethodEntry = MethodEntryHook;
cb.MethodExit = MethodExitHook;
+ cb.ClassPrepare = ClassPrepareHook;
if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
LOG(ERROR) << "Unable to set class file load hook cb!";
return 1;
@@ -503,6 +518,14 @@
LOG(ERROR) << "Unable to enable JVMTI_EVENT_VM_INIT event!";
return 1;
}
+ if (data->trace_stress) {
+ if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_CLASS_PREPARE,
+ nullptr) != JVMTI_ERROR_NONE) {
+ LOG(ERROR) << "Unable to enable CLASS_PREPARE event!";
+ return 1;
+ }
+ }
if (data->redefine_stress) {
if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
diff --git a/tools/add_package_property.sh b/tools/add_package_property.sh
old mode 100644
new mode 100755
diff --git a/tools/bootjars.sh b/tools/bootjars.sh
new file mode 100755
index 0000000..bb47e55
--- /dev/null
+++ b/tools/bootjars.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+#
+# 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.
+
+#
+# This script lists the boot jars that an ART bootclasspath would need.
+#
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+TOP="$DIR/../.."
+
+source "${TOP}/build/envsetup.sh" >&/dev/null # import get_build_var
+
+selected_env_var=
+core_jars_only=n
+print_file_path=n
+mode=target
+while true; do
+ case $1 in
+ --help)
+ echo "Usage: $0 [--core] [--path] [--host|--target] [--help]"
+ exit 0
+ ;;
+ --core)
+ core_jars_only=y
+ ;;
+ --path)
+ print_file_path=y
+ ;;
+ --host)
+ mode=host
+ ;;
+ --target)
+ mode=target
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+if [[ $mode == target ]]; then
+ if [[ $core_jars_only == y ]]; then
+ selected_env_var=TARGET_CORE_JARS
+ else
+ selected_env_var=PRODUCT_BOOT_JARS
+ fi
+ intermediates_env_var=TARGET_OUT_COMMON_INTERMEDIATES
+elif [[ $mode == host ]]; then
+ if [[ $core_jars_only == n ]]; then
+ echo "Error: --host does not have non-core boot jars, --core required" >&2
+ exit 1
+ fi
+ selected_env_var=HOST_CORE_JARS
+ intermediates_env_var=HOST_OUT_COMMON_INTERMEDIATES
+fi
+
+boot_jars_list=$(get_build_var "$selected_env_var")
+
+# Print only the list of boot jars.
+if [[ $print_file_path == n ]]; then
+ echo $boot_jars_list
+ exit 0
+fi
+
+# Print the file path (relative to $TOP) to the classes.jar of each boot jar in the intermediates directory.
+intermediates_dir=$(get_build_var "$intermediates_env_var")
+
+# turn the file path into an absolute path
+intermediates_dir=$(readlink -f $TOP/$intermediates_dir)
+
+for jar in $boot_jars_list; do
+ echo "$intermediates_dir/JAVA_LIBRARIES/${jar}_intermediates/classes.jar"
+done
diff --git a/tools/desugar.sh b/tools/desugar.sh
new file mode 100755
index 0000000..43541aa
--- /dev/null
+++ b/tools/desugar.sh
@@ -0,0 +1,91 @@
+#!/bin/bash
+#
+# 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.
+
+#
+# Calls desugar.jar with the --bootclasspath_entry values passed in automatically.
+# (This avoids having to manually set a boot class path).
+#
+#
+# Script-specific args:
+# --mode=[host|target]: Select between host or target bootclasspath (default target).
+# --core-only: Use only "core" bootclasspath (e.g. do not include framework).
+# --show-commands: Print the desugar command being executed.
+# --help: Print above list of args.
+#
+# All other args are forwarded to desugar.jar
+#
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+TOP=$DIR/../..
+
+pushd "$TOP" >/dev/null # back to android root.
+
+out=${OUT_DIR:-out}
+desugar_jar=$out/host/linux-x86/framework/desugar.jar
+
+if ! [[ -f $desugar_jar ]]; then
+ echo "Error: Missing $desugar_jar; did you do a build?" >&2
+ exit 1
+fi
+
+desugar_jar=$(readlink -f "$desugar_jar") # absolute path to desugar jar
+popd >/dev/null
+
+bootjars_args=
+mode=target
+showcommands=n
+while true; do
+ case $1 in
+ --help)
+ echo "Usage: $0 [--mode=host|target] [--core-only] [--show-commands] <desugar args>"
+ exit 0
+ ;;
+ --mode=host)
+ bootjars_args="$bootjars_args --host"
+ ;;
+ --mode=target)
+ bootjars_args="$bootjars_args --target"
+ ;;
+ --core-only)
+ bootjars_args="$bootjars_args --core"
+ ;;
+ --show-commands)
+ showcommands=y
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+desugar_args=()
+boot_class_path_list=$($TOP/art/tools/bootjars.sh $bootjars_args --path)
+
+for path in $boot_class_path_list; do
+ desugar_args+=(--bootclasspath_entry="$path")
+done
+
+if [[ ${#desugar_args[@]} -eq 0 ]]; then
+ echo "FATAL: Missing bootjars.sh file path list" >&2
+ exit 1
+fi
+
+if [[ $showcommands == y ]]; then
+ echo java -jar "$desugar_jar" "${desugar_args[@]}" "$@"
+fi
+
+java -jar "$desugar_jar" "${desugar_args[@]}" "$@"
diff --git a/tools/runtime_memusage/README b/tools/runtime_memusage/README
new file mode 100644
index 0000000..2543df1
--- /dev/null
+++ b/tools/runtime_memusage/README
@@ -0,0 +1,72 @@
+Dex File Poisoning Access
+=========================
+
+These set of executables are useful for condensing large amounts of memory reads
+of Dex Files into smaller, split pieces of information. Two kinds of information
+are provided:
+ 1. Visualizing what part of a Dex File is being accessed at what time
+ as a graph
+ 2. Ordering stack traces by most commonly occurring
+Both of these kinds of information can be split up further by providing category
+names as arguments. A trace is put into a category if the category name is a
+substring of the symbolized trace.
+
+How:
+======
+These set of tools work in conjunction with the class
+DexFileTrackingRegistrar, which marks sections of Dex Files as poisoned. As Dex
+Files are marked for poisoning, their starting addresses are logged in logcat.
+In addition, when poisoned sections of memory are accesses, their stack trace is
+also outputted to logcat.
+
+sanitizer_logcat_analysis.sh is the main executable that will use the other two
+in order to give both types of information. The other two are used in some of
+the intermediary steps which are described in sanitizer_logcat_analysis.sh,
+though they can also be executed individually if provided the necessary input.
+
+Why:
+======
+
+The main reason for splitting the functionality across multiple files is because
+sanitizer_logcat_analysis.sh uses external executable development/scripts/stack.
+This is necessary in order to get symbolized traces from the output given by
+Address Sanitizer.
+
+How to Use:
+
+sanitizer_logcat_analysis.sh at minimum requires all logcat output in the form
+of a file. Additional options specified below are useful for removing
+unnecessary trace information.
+
+===========================================================================
+Usage: sanitizer_logcat_analysis.sh [options] [LOGCAT_FILE] [CATEGORIES...]
+ -d OUT_DIRECTORY
+ Puts all output in specified directory.
+ If not given, output will be put in a local
+ temp folder which will be deleted after
+ execution.
+
+ -e
+ All traces will have exactly the same number
+ of categories which is specified by either
+ the -m argument or by prune_sanitizer_output.py
+
+ -f
+ forces redo of all commands even if output
+ files exist.
+
+ -m [MINIMUM_CALLS_PER_TRACE]
+ Filters out all traces that do not have
+ at least MINIMUM_CALLS_PER_TRACE lines.
+ default: specified by prune_sanitizer_output.py
+
+ CATEGORIES are words that are expected to show in
+ a large subset of symbolized traces. Splits
+ output based on each word.
+
+ LOGCAT_FILE is the piped output from adb logcat.
+===========================================================================
+
+
+
+
diff --git a/tools/runtime_memusage/prune_sanitizer_output.py b/tools/runtime_memusage/prune_sanitizer_output.py
new file mode 100755
index 0000000..222c3c7
--- /dev/null
+++ b/tools/runtime_memusage/prune_sanitizer_output.py
@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+#
+# 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.
+
+"""Cleans up overlapping portions of traces provided by logcat."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import argparse
+import os
+import sys
+
+STACK_DIVIDER = 65 * '='
+
+
+def has_min_lines(trace, stack_min_size):
+ """Checks if the trace has a minimum amount of levels in trace."""
+ # Line containing 'use-after-poison' contains address accessed, which is
+ # useful for extracting Dex File offsets
+ string_checks = ['use-after-poison', 'READ']
+ required_checks = string_checks + ['#%d ' % line_ctr
+ for line_ctr in
+ range(stack_min_size)
+ ]
+ try:
+ trace_indices = [trace.index(check) for check in required_checks]
+ return all(trace_indices[trace_ind] < trace_indices[trace_ind + 1]
+ for trace_ind in range(len(trace_indices) - 1))
+ except ValueError:
+ return False
+ return True
+
+
+def prune_exact(trace, stack_min_size):
+ """Removes all of trace that comes after the (stack_min_size)th trace."""
+ string_checks = ['use-after-poison', 'READ']
+ required_checks = string_checks + ['#%d ' % line_ctr
+ for line_ctr in
+ range(stack_min_size)
+ ]
+ trace_indices = [trace.index(check) for check in required_checks]
+ new_line_index = trace.index("\n", trace_indices[-1])
+ return trace[:new_line_index + 1]
+
+
+def make_unique(trace):
+ """Removes overlapping line numbers and lines out of order."""
+ string_checks = ['use-after-poison', 'READ']
+ hard_checks = string_checks + ['#%d ' % line_ctr
+ for line_ctr in range(100)
+ ]
+ last_ind = -1
+ for str_check in hard_checks:
+ try:
+ location_ind = trace.index(str_check)
+ if last_ind > location_ind:
+ trace = trace[:trace[:location_ind].find("\n") + 1]
+ last_ind = location_ind
+ try:
+ next_location_ind = trace.index(str_check, location_ind + 1)
+ trace = trace[:next_location_ind]
+ except ValueError:
+ pass
+ except ValueError:
+ pass
+ return trace
+
+
+def parse_args(argv):
+ """Parses arguments passed in."""
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-d', action='store',
+ default="", dest="out_dir_name", type=is_directory,
+ help='Output Directory')
+ parser.add_argument('-e', action='store_true',
+ default=False, dest='check_exact',
+ help='Forces each trace to be cut to have '
+ 'minimum number of lines')
+ parser.add_argument('-m', action='store',
+ default=4, dest='stack_min_size', type=int,
+ help='minimum number of lines a trace should have')
+ parser.add_argument('trace_file', action='store',
+ type=argparse.FileType('r'),
+ help='File only containing lines that are related to '
+ 'Sanitizer traces')
+ return parser.parse_args(argv)
+
+
+def is_directory(path_name):
+ """Checks if a path is an actual directory."""
+ if not os.path.isdir(path_name):
+ dir_error = "%s is not a directory" % (path_name)
+ raise argparse.ArgumentTypeError(dir_error)
+ return path_name
+
+
+def main(argv=None):
+ """Parses arguments and cleans up traces using other functions."""
+ stack_min_size = 4
+ check_exact = False
+
+ if argv is None:
+ argv = sys.argv
+
+ parsed_argv = parse_args(argv[1:])
+ stack_min_size = parsed_argv.stack_min_size
+ check_exact = parsed_argv.check_exact
+ out_dir_name = parsed_argv.out_dir_name
+ trace_file = parsed_argv.trace_file
+
+ trace_split = trace_file.read().split(STACK_DIVIDER)
+ trace_file.close()
+ # if flag -e is enabled
+ if check_exact:
+ trace_prune_split = [prune_exact(trace, stack_min_size)
+ for trace in trace_split if
+ has_min_lines(trace, stack_min_size)
+ ]
+ trace_unique_split = [make_unique(trace)
+ for trace in trace_prune_split
+ ]
+ else:
+ trace_unique_split = [make_unique(trace)
+ for trace in trace_split if
+ has_min_lines(trace, stack_min_size)
+ ]
+ # has_min_lines is called again because removing lines can prune too much
+ trace_clean_split = [trace for trace
+ in trace_unique_split if
+ has_min_lines(trace,
+ stack_min_size)
+ ]
+
+ outfile = os.path.join(out_dir_name, trace_file.name + '_filtered')
+ with open(outfile, "w") as output_file:
+ output_file.write(STACK_DIVIDER.join(trace_clean_split))
+
+ filter_percent = 100.0 - (float(len(trace_clean_split)) /
+ len(trace_split) * 100)
+ filter_amount = len(trace_split) - len(trace_clean_split)
+ print("Filtered out %d (%f%%) of %d."
+ % (filter_amount, filter_percent, len(trace_split)))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/runtime_memusage/sanitizer_logcat_analysis.sh b/tools/runtime_memusage/sanitizer_logcat_analysis.sh
new file mode 100755
index 0000000..75cb9a9
--- /dev/null
+++ b/tools/runtime_memusage/sanitizer_logcat_analysis.sh
@@ -0,0 +1,180 @@
+#!/bin/sh
+#
+# 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.
+#
+# Note: Requires $ANDROID_BUILD_TOP/build/envsetup.sh to have been run.
+#
+# This script takes in a logcat containing Sanitizer traces and outputs several
+# files, prints information regarding the traces, and plots information as well.
+USE_TEMP=true
+DO_REDO=false
+# EXACT_ARG and MIN_ARG are passed to prune_sanitizer_output.py
+EXACT_ARG=""
+MIN_ARG=""
+usage() {
+ echo "Usage: $0 [options] [LOGCAT_FILE] [CATEGORIES...]"
+ echo " -d OUT_DIRECTORY"
+ echo " Puts all output in specified directory."
+ echo " If not given, output will be put in a local"
+ echo " temp folder which will be deleted after"
+ echo " execution."
+ echo
+ echo " -e"
+ echo " All traces will have exactly the same number"
+ echo " of categories which is specified by either"
+ echo " the -m argument or by prune_sanitizer_output.py"
+ echo
+ echo " -f"
+ echo " forces redo of all commands even if output"
+ echo " files exist. Steps are skipped if their output"
+ echo " exist already and this is not enabled."
+ echo
+ echo " -m [MINIMUM_CALLS_PER_TRACE]"
+ echo " Filters out all traces that do not have"
+ echo " at least MINIMUM_CALLS_PER_TRACE lines."
+ echo " default: specified by prune_sanitizer_output.py"
+ echo
+ echo " CATEGORIES are words that are expected to show in"
+ echo " a large subset of symbolized traces. Splits"
+ echo " output based on each word."
+ echo
+ echo " LOGCAT_FILE is the piped output from adb logcat."
+ echo
+}
+
+
+while [[ $# -gt 1 ]]; do
+case $1 in
+ -d)
+ shift
+ USE_TEMP=false
+ OUT_DIR=$1
+ shift
+ break
+ ;;
+ -e)
+ shift
+ EXACT_ARG='-e'
+ ;;
+ -f)
+ shift
+ DO_REDO=true
+ ;;
+ -m)
+ shift
+ MIN_ARG='-m '"$1"''
+ shift
+ ;;
+ *)
+ usage
+ exit
+esac
+done
+
+if [ $# -lt 1 ]; then
+ usage
+ exit
+fi
+
+LOGCAT_FILE=$1
+NUM_CAT=$(($# - 1))
+
+# Use a temp directory that will be deleted
+if [ $USE_TEMP = true ]; then
+ OUT_DIR=$(mktemp -d --tmpdir=$PWD)
+ DO_REDO=true
+fi
+
+if [ ! -d "$OUT_DIR" ]; then
+ mkdir $OUT_DIR
+ DO_REDO=true
+fi
+
+# Note: Steps are skipped if their output exists until -f flag is enabled
+# Step 1 - Only output lines related to Sanitizer
+# Folder that holds all file output
+echo "Output folder: $OUT_DIR"
+ASAN_OUT=$OUT_DIR/asan_output
+if [ ! -f $ASAN_OUT ] || [ $DO_REDO = true ]; then
+ DO_REDO=true
+ echo "Extracting ASAN output"
+ grep "app_process64" $LOGCAT_FILE > $ASAN_OUT
+else
+ echo "Skipped: Extracting ASAN output"
+fi
+
+# Step 2 - Only output lines containing Dex File Start Addresses
+DEX_START=$OUT_DIR/dex_start
+if [ ! -f $DEX_START ] || [ $DO_REDO = true ]; then
+ DO_REDO=true
+ echo "Extracting Start of Dex File(s)"
+ grep "RegisterDexFile" $LOGCAT_FILE > $DEX_START
+else
+ echo "Skipped: Extracting Start of Dex File(s)"
+fi
+
+# Step 3 - Clean Sanitizer output from Step 2 since logcat cannot
+# handle large amounts of output.
+ASAN_OUT_FILTERED=$OUT_DIR/asan_output_filtered
+if [ ! -f $ASAN_OUT_FILTERED ] || [ $DO_REDO = true ]; then
+ DO_REDO=true
+ echo "Filtering/Cleaning ASAN output"
+ python $ANDROID_BUILD_TOP/art/tools/runtime_memusage/prune_sanitizer_output.py \
+ $EXACT_ARG $MIN_ARG -d $OUT_DIR $ASAN_OUT
+else
+ echo "Skipped: Filtering/Cleaning ASAN output"
+fi
+
+# Step 4 - Retrieve symbolized stack traces from Step 3 output
+SYM_FILTERED=$OUT_DIR/sym_filtered
+if [ ! -f $SYM_FILTERED ] || [ $DO_REDO = true ]; then
+ DO_REDO=true
+ echo "Retrieving symbolized traces"
+ $ANDROID_BUILD_TOP/development/scripts/stack $ASAN_OUT_FILTERED > $SYM_FILTERED
+else
+ echo "Skipped: Retrieving symbolized traces"
+fi
+
+# Step 5 - Using Steps 2, 3, 4 outputs in order to output graph data
+# and trace data
+# Only the category names are needed for the commands giving final output
+shift
+TIME_OUTPUT=($OUT_DIR/time_output_*.dat)
+if [ ! -e ${TIME_OUTPUT[0]} ] || [ $DO_REDO = true ]; then
+ DO_REDO=true
+ echo "Creating Categorized Time Table"
+ python $ANDROID_BUILD_TOP/art/tools/runtime_memusage/symbol_trace_info.py \
+ -d $OUT_DIR $ASAN_OUT_FILTERED $SYM_FILTERED $DEX_START $@
+else
+ echo "Skipped: Creating Categorized Time Table"
+fi
+
+# Step 6 - Use graph data from Step 5 to plot graph
+# Contains the category names used for legend of gnuplot
+PLOT_CATS=`echo \"Uncategorized $@\"`
+echo "Plotting Categorized Time Table"
+# Plots the information from logcat
+gnuplot --persist -e \
+ 'filename(n) = sprintf("'"$OUT_DIR"'/time_output_%d.dat", n);
+ catnames = '"$PLOT_CATS"';
+ set title "Dex File Offset vs. Time accessed since App Start";
+ set xlabel "Time (milliseconds)";
+ set ylabel "Dex File Offset (bytes)";
+ plot for [i=0:'"$NUM_CAT"'] filename(i) using 1:2 title word(catnames, i + 1);'
+
+if [ $USE_TEMP = true ]; then
+ echo "Removing temp directory and files"
+ rm -rf $OUT_DIR
+fi
diff --git a/tools/runtime_memusage/symbol_trace_info.py b/tools/runtime_memusage/symbol_trace_info.py
new file mode 100755
index 0000000..57ed6ce
--- /dev/null
+++ b/tools/runtime_memusage/symbol_trace_info.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+#
+# 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.
+
+"""Outputs quantitative information about Address Sanitizer traces."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from collections import Counter
+from datetime import datetime
+import argparse
+import bisect
+import os
+import sys
+
+
+def find_match(list_substrings, big_string):
+ """Returns the category a trace belongs to by searching substrings."""
+ for ind, substr in enumerate(list_substrings):
+ if big_string.find(substr) != -1:
+ return ind
+ return list_substrings.index("Uncategorized")
+
+
+def absolute_to_relative(plot_list, dex_start_list, cat_list):
+ """Address changed to Dex File offset and shifting time to 0 min in ms."""
+ time_format_str = "%H:%M:%S.%f"
+ first_access_time = datetime.strptime(plot_list[0][0],
+ time_format_str)
+ for ind, elem in enumerate(plot_list):
+ elem_date_time = datetime.strptime(elem[0], time_format_str)
+ # Shift time values so that first access is at time 0 milliseconds
+ elem[0] = int((elem_date_time - first_access_time).total_seconds() *
+ 1000)
+ address_access = int(elem[1], 16)
+ # For each poisoned address, find highest Dex File starting address less
+ # than address_access
+ dex_file_start = dex_start_list[bisect.bisect(dex_start_list,
+ address_access) - 1
+ ]
+ elem.insert(1, address_access - dex_file_start)
+ # Category that a data point belongs to
+ elem.insert(2, cat_list[ind])
+
+
+def print_category_info(cat_split, outname, out_dir_name, title):
+ """Prints information of category and puts related traces in a files."""
+ trace_counts_dict = Counter(cat_split)
+ trace_counts_list_ordered = trace_counts_dict.most_common()
+ print(53 * "-")
+ print(title)
+ print("\tNumber of distinct traces: " +
+ str(len(trace_counts_list_ordered)))
+ print("\tSum of trace counts: " +
+ str(sum([trace[1] for trace in trace_counts_list_ordered])))
+ print("\n\tCount: How many traces appeared with count\n\t")
+ print(Counter([trace[1] for trace in trace_counts_list_ordered]))
+ with open(os.path.join(out_dir_name, outname), "w") as output_file:
+ for trace in trace_counts_list_ordered:
+ output_file.write("\n\nNumber of times appeared: " +
+ str(trace[1]) +
+ "\n")
+ output_file.write(trace[0].strip())
+
+
+def print_categories(categories, symbol_file_split, out_dir_name):
+ """Prints details of all categories."""
+ # Info of traces containing a call to current category
+ for cat_num, cat_name in enumerate(categories[1:]):
+ print("\nCategory #%d" % (cat_num + 1))
+ cat_split = [trace for trace in symbol_file_split
+ if cat_name in trace]
+ cat_file_name = cat_name.lower() + "cat_output"
+ print_category_info(cat_split, cat_file_name, out_dir_name,
+ "Traces containing: " + cat_name)
+ noncat_split = [trace for trace in symbol_file_split
+ if cat_name not in trace]
+ print_category_info(noncat_split, "non" + cat_file_name,
+ out_dir_name,
+ "Traces not containing: " +
+ cat_name)
+
+ # All traces (including uncategorized) together
+ print_category_info(symbol_file_split, "allcat_output",
+ out_dir_name,
+ "All traces together:")
+ # Traces containing none of keywords
+ # Only used if categories are passed in
+ if len(categories) > 1:
+ noncat_split = [trace for trace in symbol_file_split if
+ all(cat_name not in trace
+ for cat_name in categories)]
+ print_category_info(noncat_split, "noncat_output",
+ out_dir_name,
+ "Uncategorized calls")
+
+
+def is_directory(path_name):
+ """Checks if a path is an actual directory."""
+ if not os.path.isdir(path_name):
+ dir_error = "%s is not a directory" % (path_name)
+ raise argparse.ArgumentTypeError(dir_error)
+ return path_name
+
+
+def parse_args(argv):
+ """Parses arguments passed in."""
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-d', action='store',
+ default="", dest="out_dir_name", type=is_directory,
+ help='Output Directory')
+ parser.add_argument('sanitizer_trace', action='store',
+ type=argparse.FileType('r'),
+ help='File containing sanitizer traces filtered by '
+ 'prune_sanitizer_output.py')
+ parser.add_argument('symbol_trace', action='store',
+ type=argparse.FileType('r'),
+ help='File containing symbolized traces that match '
+ 'sanitizer_trace')
+ parser.add_argument('dex_starts', action='store',
+ type=argparse.FileType('r'),
+ help='File containing starting addresses of Dex Files')
+ parser.add_argument('categories', action='store', nargs='*',
+ help='Keywords expected to show in large amounts of'
+ ' symbolized traces')
+
+ return parser.parse_args(argv)
+
+
+def read_data(parsed_argv):
+ """Reads data from filepath arguments and parses them into lists."""
+ # Using a dictionary to establish relation between lists added
+ data_lists = {}
+ categories = parsed_argv.categories
+ # Makes sure each trace maps to some category
+ categories.insert(0, "Uncategorized")
+
+ logcat_file_data = parsed_argv.sanitizer_trace.readlines()
+ parsed_argv.sanitizer_trace.close()
+
+ symbol_file_split = parsed_argv.symbol_trace.read().split("Stack Trace")[
+ 1:]
+ parsed_argv.symbol_trace.close()
+
+ dex_start_file_data = parsed_argv.dex_starts.readlines()
+ parsed_argv.dex_starts.close()
+
+ # Each element is a tuple of time and address accessed
+ data_lists["plot_list"] = [[elem[1] for elem in enumerate(line.split())
+ if elem[0] in (1, 11)
+ ]
+ for line in logcat_file_data
+ if "use-after-poison" in line
+ ]
+ # Contains a mapping between traces and the category they belong to
+ # based on arguments
+ data_lists["cat_list"] = [categories[find_match(categories, trace)]
+ for trace in symbol_file_split]
+
+ # Contains a list of starting address of all dex files to calculate dex
+ # offsets
+ data_lists["dex_start_list"] = [int(line.split("@")[1], 16)
+ for line in dex_start_file_data
+ if "RegisterDexFile" in line
+ ]
+ return data_lists, categories, symbol_file_split
+
+
+def main(argv=None):
+ """Takes in trace information and outputs details about them."""
+ if argv is None:
+ argv = sys.argv
+ parsed_argv = parse_args(argv[1:])
+
+ data_lists, categories, symbol_file_split = read_data(parsed_argv)
+ # Formats plot_list such that each element is a data point
+ absolute_to_relative(data_lists["plot_list"], data_lists["dex_start_list"],
+ data_lists["cat_list"])
+ for file_ext, cat_name in enumerate(categories):
+ out_file_name = os.path.join(parsed_argv.out_dir_name, "time_output_" +
+ str(file_ext) +
+ ".dat")
+ with open(out_file_name, "w") as output_file:
+ output_file.write("# Category: " + cat_name + "\n")
+ output_file.write("# Time, Dex File Offset, Address \n")
+ for time, dex_offset, category, address in data_lists["plot_list"]:
+ if category == cat_name:
+ output_file.write(
+ str(time) +
+ " " +
+ str(dex_offset) +
+ " #" +
+ str(address) +
+ "\n")
+
+ print_categories(categories, symbol_file_split, parsed_argv.out_dir_name)
+
+
+if __name__ == '__main__':
+ main()