Fix various debugger method invocation bugs.
(Also fix DexFile to admit that string lengths are always non-negative.)
Change-Id: I2d01ef2411b5c7e594527790daf3e0aaa3a1b67f
diff --git a/src/debugger.cc b/src/debugger.cc
index a456f23..494ee73 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -1991,7 +1991,35 @@
gSingleStepControl.dex_pcs.clear();
}
-JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId threadId, JDWP::ObjectId objectId, JDWP::RefTypeId classId, JDWP::MethodId methodId, uint32_t numArgs, uint64_t* argArray, uint32_t options, JDWP::JdwpTag* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptionId) {
+static char JdwpTagToShortyChar(JDWP::JdwpTag tag) {
+ switch (tag) {
+ default:
+ LOG(FATAL) << "unknown JDWP tag: " << PrintableChar(tag);
+
+ // Primitives.
+ case JDWP::JT_BYTE: return 'B';
+ case JDWP::JT_CHAR: return 'C';
+ case JDWP::JT_FLOAT: return 'F';
+ case JDWP::JT_DOUBLE: return 'D';
+ case JDWP::JT_INT: return 'I';
+ case JDWP::JT_LONG: return 'J';
+ case JDWP::JT_SHORT: return 'S';
+ case JDWP::JT_VOID: return 'V';
+ case JDWP::JT_BOOLEAN: return 'Z';
+
+ // Reference types.
+ case JDWP::JT_ARRAY:
+ case JDWP::JT_OBJECT:
+ case JDWP::JT_STRING:
+ case JDWP::JT_THREAD:
+ case JDWP::JT_THREAD_GROUP:
+ case JDWP::JT_CLASS_LOADER:
+ case JDWP::JT_CLASS_OBJECT:
+ return 'L';
+ }
+}
+
+JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId threadId, JDWP::ObjectId objectId, JDWP::RefTypeId classId, JDWP::MethodId methodId, uint32_t arg_count, uint64_t* arg_values, JDWP::JdwpTag* arg_types, uint32_t options, JDWP::JdwpTag* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptionId) {
ThreadList* thread_list = Runtime::Current()->GetThreadList();
Thread* targetThread = NULL;
@@ -2030,22 +2058,54 @@
}
JDWP::JdwpError status;
- req->receiver_ = gRegistry->Get<Object*>(objectId);
- if (req->receiver_ == kInvalidObject) {
+ Object* receiver = gRegistry->Get<Object*>(objectId);
+ if (receiver == kInvalidObject) {
return JDWP::ERR_INVALID_OBJECT;
}
- req->thread_ = gRegistry->Get<Object*>(threadId); // TODO: check that this is actually a thread!
- if (req->thread_ == kInvalidObject) {
+
+ Object* thread = gRegistry->Get<Object*>(threadId);
+ if (thread == kInvalidObject) {
return JDWP::ERR_INVALID_OBJECT;
}
- req->class_ = DecodeClass(classId, status);
- if (req->class_ == NULL) {
+ // TODO: check that 'thread' is actually a java.lang.Thread!
+
+ Class* c = DecodeClass(classId, status);
+ if (c == NULL) {
return status;
}
- req->method_ = FromMethodId(methodId);
- req->num_args_ = numArgs;
- // TODO: check that the argument list is valid.
- req->arg_array_ = argArray;
+
+ Method* m = FromMethodId(methodId);
+ if (m->IsStatic() != (receiver == NULL)) {
+ return JDWP::ERR_INVALID_METHODID;
+ }
+ if (m->IsStatic()) {
+ if (m->GetDeclaringClass() != c) {
+ return JDWP::ERR_INVALID_METHODID;
+ }
+ } else {
+ if (!m->GetDeclaringClass()->IsAssignableFrom(c)) {
+ return JDWP::ERR_INVALID_METHODID;
+ }
+ }
+
+ // Check the argument list matches the method.
+ MethodHelper mh(m);
+ if (mh.GetShortyLength() - 1 != arg_count) {
+ return JDWP::ERR_ILLEGAL_ARGUMENT;
+ }
+ const char* shorty = mh.GetShorty();
+ for (size_t i = 0; i < arg_count; ++i) {
+ if (shorty[i + 1] != JdwpTagToShortyChar(arg_types[i])) {
+ return JDWP::ERR_ILLEGAL_ARGUMENT;
+ }
+ }
+
+ req->receiver_ = receiver;
+ req->thread_ = thread;
+ req->class_ = c;
+ req->method_ = m;
+ req->arg_count_ = arg_count;
+ req->arg_values_ = arg_values;
req->options_ = options;
req->invoke_needed_ = true;
}
@@ -2124,16 +2184,20 @@
// Translate the method through the vtable, unless the debugger wants to suppress it.
Method* m = pReq->method_;
- VLOG(jdwp) << "ExecuteMethod " << PrettyMethod(m);
if ((pReq->options_ & JDWP::INVOKE_NONVIRTUAL) == 0 && pReq->receiver_ != NULL) {
- m = pReq->class_->FindVirtualMethodForVirtualOrInterface(pReq->method_);
- VLOG(jdwp) << "ExecuteMethod " << PrettyMethod(m);
+ Method* actual_method = pReq->class_->FindVirtualMethodForVirtualOrInterface(pReq->method_);
+ if (actual_method != m) {
+ VLOG(jdwp) << "ExecuteMethod translated " << PrettyMethod(m) << " to " << PrettyMethod(actual_method);
+ m = actual_method;
+ }
}
+ VLOG(jdwp) << "ExecuteMethod " << PrettyMethod(m);
CHECK(m != NULL);
CHECK_EQ(sizeof(jvalue), sizeof(uint64_t));
- pReq->result_value = InvokeWithJValues(self, pReq->receiver_, m, reinterpret_cast<JValue*>(pReq->arg_array_));
+ LOG(INFO) << "self=" << self << " pReq->receiver_=" << pReq->receiver_ << " m=" << m << " #" << pReq->arg_count_ << " " << pReq->arg_values_;
+ pReq->result_value = InvokeWithJValues(self, pReq->receiver_, m, reinterpret_cast<JValue*>(pReq->arg_values_));
pReq->exception = gRegistry->Add(self->GetException());
pReq->result_tag = BasicTagFromDescriptor(MethodHelper(m).GetShorty());
diff --git a/src/debugger.h b/src/debugger.h
index 1e5ca8f..b88edbb 100644
--- a/src/debugger.h
+++ b/src/debugger.h
@@ -54,8 +54,8 @@
Object* thread_;
Class* class_;
Method* method_;
- uint32_t num_args_;
- uint64_t* arg_array_; /* will be NULL if numArgs==0 */
+ uint32_t arg_count_;
+ uint64_t* arg_values_; /* will be NULL if arg_count_ == 0 */
uint32_t options_;
/* result */
@@ -228,7 +228,7 @@
static JDWP::JdwpError ConfigureStep(JDWP::ObjectId threadId, JDWP::JdwpStepSize size, JDWP::JdwpStepDepth depth);
static void UnconfigureStep(JDWP::ObjectId threadId);
- static JDWP::JdwpError InvokeMethod(JDWP::ObjectId threadId, JDWP::ObjectId objectId, JDWP::RefTypeId classId, JDWP::MethodId methodId, uint32_t numArgs, uint64_t* argArray, uint32_t options, JDWP::JdwpTag* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptObj);
+ static JDWP::JdwpError InvokeMethod(JDWP::ObjectId threadId, JDWP::ObjectId objectId, JDWP::RefTypeId classId, JDWP::MethodId methodId, uint32_t arg_count, uint64_t* arg_values, JDWP::JdwpTag* arg_types, uint32_t options, JDWP::JdwpTag* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptObj);
static void ExecuteMethod(DebugInvokeReq* pReq);
/* perform "late registration" of an object ID */
diff --git a/src/dex_file.cc b/src/dex_file.cc
index 159065e..7b031f2 100644
--- a/src/dex_file.cc
+++ b/src/dex_file.cc
@@ -319,7 +319,7 @@
}
// Returns a pointer to the UTF-8 string data referred to by the given string_id.
-const char* DexFile::GetStringDataAndLength(const StringId& string_id, int32_t* length) const {
+const char* DexFile::GetStringDataAndLength(const StringId& string_id, uint32_t* length) const {
DCHECK(length != NULL) << GetLocation();
const byte* ptr = begin_ + string_id.string_data_off_;
*length = DecodeUnsignedLeb128(&ptr);
@@ -427,7 +427,7 @@
uint32_t hi = NumStringIds() - 1;
while (hi >= lo) {
uint32_t mid = (hi + lo) / 2;
- int32_t length;
+ uint32_t length;
const DexFile::StringId& str_id = GetStringId(mid);
const char* str = GetStringDataAndLength(str_id, &length);
int compare = CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues(string.c_str(), str);
@@ -563,7 +563,7 @@
for (size_t i = 0; i < type_list->Size(); ++i) {
const TypeItem& type_item = type_list->GetTypeItem(i);
uint32_t type_idx = type_item.type_idx_;
- int32_t type_length;
+ uint32_t type_length;
const char* name = StringByTypeIdx(type_idx, &type_length);
parameter_length += type_length;
descriptor.append(name);
@@ -571,7 +571,7 @@
}
descriptor.push_back(')');
uint32_t return_type_idx = proto_id.return_type_idx_;
- int32_t return_type_length;
+ uint32_t return_type_length;
const char* name = StringByTypeIdx(return_type_idx, &return_type_length);
descriptor.append(name);
if (unicode_length != NULL) {
diff --git a/src/dex_file.h b/src/dex_file.h
index d66677e..429c7bc 100644
--- a/src/dex_file.h
+++ b/src/dex_file.h
@@ -385,15 +385,15 @@
int32_t GetStringLength(const StringId& string_id) const;
// Returns a pointer to the UTF-8 string data referred to by the given string_id.
- const char* GetStringDataAndLength(const StringId& string_id, int32_t* length) const;
+ const char* GetStringDataAndLength(const StringId& string_id, uint32_t* length) const;
const char* GetStringData(const StringId& string_id) const {
- int32_t length;
+ uint32_t length;
return GetStringDataAndLength(string_id, &length);
}
// return the UTF-8 encoded string with the specified string_id index
- const char* StringDataAndLengthByIdx(uint32_t idx, int32_t* unicode_length) const {
+ const char* StringDataAndLengthByIdx(uint32_t idx, uint32_t* unicode_length) const {
if (idx == kDexNoIndex) {
*unicode_length = 0;
return NULL;
@@ -403,7 +403,7 @@
}
const char* StringDataByIdx(uint32_t idx) const {
- int32_t unicode_length;
+ uint32_t unicode_length;
return StringDataAndLengthByIdx(idx, &unicode_length);
}
@@ -431,7 +431,7 @@
}
// Get the descriptor string associated with a given type index.
- const char* StringByTypeIdx(uint32_t idx, int32_t* unicode_length) const {
+ const char* StringByTypeIdx(uint32_t idx, uint32_t* unicode_length) const {
const TypeId& type_id = GetTypeId(idx);
return StringDataAndLengthByIdx(type_id.descriptor_idx_, unicode_length);
}
@@ -537,7 +537,7 @@
const char* GetMethodShorty(const MethodId& method_id) const {
return StringDataByIdx(GetProtoId(method_id.proto_idx_).shorty_idx_);
}
- const char* GetMethodShorty(const MethodId& method_id, int32_t* length) const {
+ const char* GetMethodShorty(const MethodId& method_id, uint32_t* length) const {
return StringDataAndLengthByIdx(GetProtoId(method_id.proto_idx_).shorty_idx_, length);
}
// Returns the number of class definitions in the .dex file.
diff --git a/src/jdwp/jdwp_handler.cc b/src/jdwp/jdwp_handler.cc
index 4640d98..d6b9bb3 100644
--- a/src/jdwp/jdwp_handler.cc
+++ b/src/jdwp/jdwp_handler.cc
@@ -96,58 +96,54 @@
/*
* Common code for *_InvokeMethod requests.
*
- * If "isConstructor" is set, this returns "objectId" rather than the
+ * If "is_constructor" is set, this returns "objectId" rather than the
* expected-to-be-void return value of the called function.
*/
static JdwpError finishInvoke(JdwpState* state,
const uint8_t* buf, int dataLen, ExpandBuf* pReply,
ObjectId threadId, ObjectId objectId, RefTypeId classId, MethodId methodId,
- bool isConstructor)
+ bool is_constructor)
{
- CHECK(!isConstructor || objectId != 0);
+ CHECK(!is_constructor || objectId != 0);
- uint32_t numArgs = Read4BE(&buf);
+ uint32_t arg_count = Read4BE(&buf);
VLOG(jdwp) << StringPrintf(" --> threadId=%llx objectId=%llx", threadId, objectId);
VLOG(jdwp) << StringPrintf(" classId=%llx methodId=%x %s.%s", classId, methodId, Dbg::GetClassName(classId).c_str(), Dbg::GetMethodName(classId, methodId).c_str());
- VLOG(jdwp) << StringPrintf(" %d args:", numArgs);
+ VLOG(jdwp) << StringPrintf(" %d args:", arg_count);
- uint64_t* argArray = NULL;
- if (numArgs > 0) {
- argArray = (ObjectId*) malloc(sizeof(ObjectId) * numArgs);
- }
-
- for (uint32_t i = 0; i < numArgs; i++) {
- JDWP::JdwpTag typeTag = ReadTag(&buf);
- size_t width = Dbg::GetTagWidth(typeTag);
- uint64_t value = jdwpReadValue(&buf, width);
-
- VLOG(jdwp) << " " << typeTag << StringPrintf("(%zd): 0x%llx", width, value);
- argArray[i] = value;
+ UniquePtr<JdwpTag[]> argTypes(arg_count > 0 ? new JdwpTag[arg_count] : NULL);
+ UniquePtr<uint64_t[]> argValues(arg_count > 0 ? new uint64_t[arg_count] : NULL);
+ for (uint32_t i = 0; i < arg_count; ++i) {
+ argTypes[i] = ReadTag(&buf);
+ size_t width = Dbg::GetTagWidth(argTypes[i]);
+ argValues[i] = jdwpReadValue(&buf, width);
+ VLOG(jdwp) << " " << argTypes[i] << StringPrintf("(%zd): 0x%llx", width, argValues[i]);
}
uint32_t options = Read4BE(&buf); /* enum InvokeOptions bit flags */
VLOG(jdwp) << StringPrintf(" options=0x%04x%s%s", options, (options & INVOKE_SINGLE_THREADED) ? " (SINGLE_THREADED)" : "", (options & INVOKE_NONVIRTUAL) ? " (NONVIRTUAL)" : "");
- JDWP::JdwpTag resultTag;
+ JdwpTag resultTag;
uint64_t resultValue;
ObjectId exceptObjId;
- JdwpError err = Dbg::InvokeMethod(threadId, objectId, classId, methodId, numArgs, argArray, options, &resultTag, &resultValue, &exceptObjId);
+ JdwpError err = Dbg::InvokeMethod(threadId, objectId, classId, methodId, arg_count, argValues.get(), argTypes.get(), options, &resultTag, &resultValue, &exceptObjId);
if (err != ERR_NONE) {
- goto bail;
+ return err;
}
if (err == ERR_NONE) {
- if (isConstructor) {
- expandBufAdd1(pReply, JT_OBJECT);
- expandBufAddObjectId(pReply, objectId);
- } else {
- size_t width = Dbg::GetTagWidth(resultTag);
+ if (is_constructor) {
+ // If we invoked a constructor (which actually returns void), return the receiver,
+ // unless we threw, in which case we return NULL.
+ resultTag = JT_OBJECT;
+ resultValue = (exceptObjId == 0) ? objectId : 0;
+ }
- expandBufAdd1(pReply, resultTag);
- if (width != 0) {
- jdwpWriteValue(pReply, width, resultValue);
- }
+ size_t width = Dbg::GetTagWidth(resultTag);
+ expandBufAdd1(pReply, resultTag);
+ if (width != 0) {
+ jdwpWriteValue(pReply, width, resultValue);
}
expandBufAdd1(pReply, JT_OBJECT);
expandBufAddObjectId(pReply, exceptObjId);
@@ -164,8 +160,6 @@
}
}
-bail:
- free(argArray);
return err;
}
diff --git a/src/object_utils.h b/src/object_utils.h
index 498b0ba..0e0d7de 100644
--- a/src/object_utils.h
+++ b/src/object_utils.h
@@ -416,7 +416,7 @@
}
return result;
}
- int32_t GetShortyLength() {
+ uint32_t GetShortyLength() {
if (shorty_ == NULL) {
GetShorty();
}
@@ -587,7 +587,7 @@
const DexFile* dex_file_;
const Method* method_;
const char* shorty_;
- int32_t shorty_len_;
+ uint32_t shorty_len_;
DISALLOW_COPY_AND_ASSIGN(MethodHelper);
};
diff --git a/src/runtime_support.cc b/src/runtime_support.cc
index d9d7e90..38d2699 100644
--- a/src/runtime_support.cc
+++ b/src/runtime_support.cc
@@ -540,7 +540,7 @@
// Fix up jobject arguments
MethodHelper mh(jni_method);
int reg_num = 2; // Current register being processed, -1 for stack arguments.
- for (int32_t i = 1; i < mh.GetShortyLength(); i++) {
+ for (uint32_t i = 1; i < mh.GetShortyLength(); i++) {
char shorty_char = mh.GetShorty()[i];
if (shorty_char == 'L') {
WorkAroundJniBugsForJobject(arg_ptr);