Skip to content

Commit e71269b

Browse files
author
Stewart Miles
committed
Fixed InvokeMethod() regression introduced by ea3e8d8
InvokeMethod() was modified in ea3e8d8 to support method overloads distiguished by type. However, this change broke the lookup of methods with named arguments. Fixes #209 Bug: 132342024 Change-Id: I151fe5c893fbbc9b7feb0cf371b0e693a91e17bc
1 parent 0a443fa commit e71269b

File tree

3 files changed

+293
-26
lines changed

3 files changed

+293
-26
lines changed

build.gradle

+7
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,13 @@ createUnityTestBatchAndNonBatch(
12851285
[buildPlugin],
12861286
new File("source/VersionHandlerImpl/test/activation"), [])
12871287

1288+
createUnityTestBatchAndNonBatch(
1289+
"testVersionHandlerReflection",
1290+
("Imports the plugin into a Unity project and tests reflection " +
1291+
"methods."),
1292+
[buildPlugin],
1293+
new File("source/VersionHandler/test/reflection"), [])
1294+
12881295
createUnityTestBatchAndNonBatch(
12891296
"testAndroidResolverAsyncResolve",
12901297
("Imports the plugin into a Unity project, runs Android resolution with " +

source/VersionHandler/src/VersionHandler.cs

+47-26
Original file line numberDiff line numberDiff line change
@@ -494,35 +494,56 @@ public static object InvokeStaticMethod(
494494
public static object InvokeMethod(
495495
Type type, object objectInstance, string methodName,
496496
object[] args, Dictionary<string, object> namedArgs = null) {
497-
Type[] argTypes = null;
498-
if (args != null && args.Length > 0) {
499-
argTypes = new Type[args.Length];
500-
for (int i = 0; i < args.Length; ++i) {
501-
argTypes[i] = args[i].GetType();
502-
}
503-
}
504-
MethodInfo method = argTypes != null ?
505-
type.GetMethod(methodName, argTypes) : type.GetMethod(methodName);
506-
ParameterInfo[] parameters = method.GetParameters();
507-
int numParameters = parameters.Length;
508-
object[] parameterValues = new object[numParameters];
509-
int numPositionalArgs = args != null ? args.Length : 0;
510-
foreach (var parameter in parameters) {
511-
int position = parameter.Position;
512-
if (position < numPositionalArgs) {
513-
parameterValues[position] = args[position];
514-
continue;
515-
}
516-
object namedValue = parameter.RawDefaultValue;
517-
if (namedArgs != null) {
518-
object overrideValue;
519-
if (namedArgs.TryGetValue(parameter.Name, out overrideValue)) {
520-
namedValue = overrideValue;
497+
object[] parameterValues = null;
498+
int numberOfPositionalArgs = args != null ? args.Length : 0;
499+
int numberOfNamedArgs = namedArgs != null ? namedArgs.Count : 0;
500+
MethodInfo foundMethod = null;
501+
foreach (var method in type.GetMethods()) {
502+
if (method.Name != methodName) continue;
503+
var parameters = method.GetParameters();
504+
int numberOfParameters = parameters.Length;
505+
parameterValues = new object[numberOfParameters];
506+
int matchedPositionalArgs = 0;
507+
int matchedNamedArgs = 0;
508+
bool matchedAllRequiredArgs = true;
509+
foreach (var parameter in parameters) {
510+
var parameterType = parameter.ParameterType;
511+
int position = parameter.Position;
512+
if (position < numberOfPositionalArgs) {
513+
var positionalArg = args[position];
514+
// If the parameter type doesn't match, ignore this method.
515+
if (positionalArg != null && parameterType != positionalArg.GetType()) break;
516+
parameterValues[position] = positionalArg;
517+
matchedPositionalArgs ++;
518+
} else if (parameter.RawDefaultValue != DBNull.Value) {
519+
object namedValue = parameter.RawDefaultValue;
520+
if (numberOfNamedArgs > 0) {
521+
object namedArg;
522+
if (namedArgs.TryGetValue(parameter.Name, out namedArg)) {
523+
// If the parameter type doesn't match, ignore this method.
524+
if (namedArg != null && parameterType != namedArg.GetType()) break;
525+
namedValue = namedArg;
526+
matchedNamedArgs ++;
527+
}
528+
}
529+
parameterValues[position] = namedValue;
530+
} else {
531+
matchedAllRequiredArgs = false;
532+
break;
521533
}
522534
}
523-
parameterValues[position] = namedValue;
535+
// If all arguments were consumed by the method, we've found a match.
536+
if (matchedAllRequiredArgs &&
537+
matchedPositionalArgs == numberOfPositionalArgs &&
538+
matchedNamedArgs == numberOfNamedArgs) {
539+
foundMethod = method;
540+
break;
541+
}
542+
}
543+
if (foundMethod == null) {
544+
throw new Exception(String.Format("Method {0}.{1} not found", type.Name, methodName));
524545
}
525-
return method.Invoke(objectInstance, parameterValues);
546+
return foundMethod.Invoke(objectInstance, parameterValues);
526547
}
527548
}
528549

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
// <copyright file="TestReflection.cs" company="Google Inc.">
2+
// Copyright (C) 2019 Google Inc. All Rights Reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// </copyright>
16+
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Collections;
20+
using System.IO;
21+
22+
using Google;
23+
24+
// Class used to test method invocation.
25+
public class Greeter {
26+
private string name;
27+
28+
public Greeter(string name) {
29+
this.name = name;
30+
}
31+
32+
public static string GenericHello() {
33+
return "Hello";
34+
}
35+
36+
public static string GenericHelloWithCustomerName(string customerName) {
37+
return String.Format("{0} {1}", GenericHello(), customerName);
38+
}
39+
40+
public static string GenericHelloWithCustomerName(int customerId) {
41+
return String.Format("{0} customer #{1}", GenericHello(), customerId);
42+
}
43+
44+
public static string GenericHelloWithPronoun(string pronoun = "There") {
45+
return String.Format("{0} {1}", GenericHello(), pronoun);
46+
}
47+
48+
public static string GenericHelloWithCustomerNameAndPronoun(string customerName,
49+
string pronoun = "There") {
50+
return String.Format("{0} {1}", GenericHelloWithPronoun(pronoun: pronoun), customerName);
51+
}
52+
53+
private string MyNameIs() {
54+
return String.Format(", my name is {0}", name);
55+
}
56+
57+
public string Hello() {
58+
return Greeter.GenericHello() + MyNameIs();
59+
}
60+
61+
public string HelloWithCustomerName(string customerName) {
62+
return Greeter.GenericHelloWithCustomerName(customerName) + MyNameIs();
63+
}
64+
65+
public string HelloWithCustomerNameAndPronoun(string customerName,
66+
string pronoun = "There") {
67+
return Greeter.GenericHelloWithCustomerNameAndPronoun(customerName,
68+
pronoun: pronoun) + MyNameIs();
69+
}
70+
}
71+
72+
[UnityEditor.InitializeOnLoad]
73+
public class TestReflection {
74+
75+
/// <summary>
76+
/// Test all reflection methods.
77+
/// </summary>
78+
static TestReflection() {
79+
// Disable stack traces for more condensed logs.
80+
UnityEngine.Application.stackTraceLogType = UnityEngine.StackTraceLogType.None;
81+
82+
// Run tests.
83+
var failures = new List<string>();
84+
foreach (var test in new Func<bool>[] {
85+
TestFindClassWithAssemblyName,
86+
TestFindClassWithoutAssemblyName,
87+
TestInvokeStaticMethodWithNoArgs,
88+
TestInvokeStaticMethodWithStringArg,
89+
TestInvokeStaticMethodWithIntArg,
90+
TestInvokeStaticMethodWithNamedArgDefault,
91+
TestInvokeStaticMethodWithNamedArg,
92+
TestInvokeStaticMethodWithArgAndNamedArgDefault,
93+
TestInvokeStaticMethodWithArgAndNamedArg,
94+
TestInvokeInstanceMethodWithNoArgs,
95+
TestInvokeInstanceMethodWithNamedArgDefault,
96+
TestInvokeInstanceMethodWithNamedArg
97+
}) {
98+
var testName = test.Method.Name;
99+
Exception exception = null;
100+
bool succeeded = false;
101+
try {
102+
UnityEngine.Debug.Log(String.Format("Running test {0}", testName));
103+
succeeded = test();
104+
} catch (Exception ex) {
105+
exception = ex;
106+
succeeded = false;
107+
}
108+
if (succeeded) {
109+
UnityEngine.Debug.Log(String.Format("{0}: PASSED", testName));
110+
} else {
111+
UnityEngine.Debug.LogError(String.Format("{0} ({1}): FAILED", testName, exception));
112+
failures.Add(testName);
113+
}
114+
}
115+
116+
if (failures.Count > 0) {
117+
UnityEngine.Debug.Log("Test failed");
118+
foreach (var testName in failures) {
119+
UnityEngine.Debug.Log(String.Format("{0}: FAILED", testName));
120+
}
121+
UnityEditor.EditorApplication.Exit(1);
122+
}
123+
UnityEngine.Debug.Log("Test passed");
124+
UnityEditor.EditorApplication.Exit(0);
125+
}
126+
127+
// Test searching for a class when specifying the assembly name.
128+
static bool TestFindClassWithAssemblyName() {
129+
var expectedType = typeof(UnityEditor.EditorApplication);
130+
var foundType = VersionHandler.FindClass("UnityEditor", "UnityEditor.EditorApplication");
131+
if (expectedType != foundType) {
132+
UnityEngine.Debug.LogError(String.Format("Unexpected type {0} vs {1}", foundType,
133+
expectedType));
134+
return false;
135+
}
136+
return true;
137+
}
138+
139+
// Test searching for a class without specifying the assembly name.
140+
static bool TestFindClassWithoutAssemblyName() {
141+
var expectedType = typeof(UnityEditor.EditorApplication);
142+
var foundType = VersionHandler.FindClass(null, "UnityEditor.EditorApplication");
143+
if (expectedType != foundType) {
144+
UnityEngine.Debug.LogError(String.Format("Unexpected type {0} vs {1}", foundType,
145+
expectedType));
146+
return false;
147+
}
148+
return true;
149+
}
150+
151+
static bool CheckValue(string expected, string value) {
152+
if (value != expected) {
153+
UnityEngine.Debug.LogError(String.Format("Unexpected value {0} vs {1}", value,
154+
expected));
155+
return false;
156+
}
157+
return true;
158+
}
159+
160+
// Invoke a static method with no arguments.
161+
static bool TestInvokeStaticMethodWithNoArgs() {
162+
return CheckValue("Hello",
163+
(string)VersionHandler.InvokeStaticMethod(typeof(Greeter), "GenericHello",
164+
null, null));
165+
}
166+
167+
// Invoke an overloaded static method with a string arg.
168+
static bool TestInvokeStaticMethodWithStringArg() {
169+
return CheckValue("Hello Jane",
170+
(string)VersionHandler.InvokeStaticMethod(typeof(Greeter),
171+
"GenericHelloWithCustomerName",
172+
new object[] { "Jane" }, null));
173+
}
174+
175+
// Invoke an overloaded static method with an int arg.
176+
static bool TestInvokeStaticMethodWithIntArg() {
177+
return CheckValue("Hello customer #1337",
178+
(string)VersionHandler.InvokeStaticMethod(typeof(Greeter),
179+
"GenericHelloWithCustomerName",
180+
new object[] { 1337 }, null));
181+
}
182+
183+
// Invoke a static method with a default value of a named arg.
184+
static bool TestInvokeStaticMethodWithNamedArgDefault() {
185+
return CheckValue("Hello There",
186+
(string)VersionHandler.InvokeStaticMethod(typeof(Greeter),
187+
"GenericHelloWithPronoun",
188+
null, null));
189+
}
190+
191+
// Invoke a static method with a named arg.
192+
static bool TestInvokeStaticMethodWithNamedArg() {
193+
return CheckValue("Hello Miss",
194+
(string)VersionHandler.InvokeStaticMethod(
195+
typeof(Greeter), "GenericHelloWithPronoun",
196+
null, new Dictionary<string, object> { { "pronoun", "Miss" } }));
197+
}
198+
199+
// Invoke a static method with a positional and default value for a named arg.
200+
static bool TestInvokeStaticMethodWithArgAndNamedArgDefault() {
201+
return CheckValue("Hello There Bob",
202+
(string)VersionHandler.InvokeStaticMethod(
203+
typeof(Greeter), "GenericHelloWithCustomerNameAndPronoun",
204+
new object[] { "Bob" }, null));
205+
}
206+
207+
// Invoke a static method with a positional and named arg.
208+
static bool TestInvokeStaticMethodWithArgAndNamedArg() {
209+
return CheckValue("Hello Mrs Smith",
210+
(string)VersionHandler.InvokeStaticMethod(
211+
typeof(Greeter), "GenericHelloWithCustomerNameAndPronoun",
212+
new object[] { "Smith" },
213+
new Dictionary<string, object> { { "pronoun", "Mrs" } } ));
214+
}
215+
216+
// Invoke an instance method with no args.
217+
static bool TestInvokeInstanceMethodWithNoArgs() {
218+
return CheckValue("Hello, my name is Sam",
219+
(string)VersionHandler.InvokeInstanceMethod(new Greeter("Sam"), "Hello",
220+
null, null));
221+
}
222+
223+
// Invoke an instance method with an default value for a named argument.
224+
static bool TestInvokeInstanceMethodWithNamedArgDefault() {
225+
return CheckValue("Hello There Helen, my name is Sam",
226+
(string)VersionHandler.InvokeInstanceMethod(
227+
new Greeter("Sam"), "HelloWithCustomerNameAndPronoun",
228+
new object[] { "Helen" }, null));
229+
}
230+
231+
// Invoke an instance method with a named argument.
232+
static bool TestInvokeInstanceMethodWithNamedArg() {
233+
return CheckValue("Hello Mrs Smith, my name is Sam",
234+
(string)VersionHandler.InvokeInstanceMethod(
235+
new Greeter("Sam"), "HelloWithCustomerNameAndPronoun",
236+
new object[] { "Smith" },
237+
new Dictionary<string, object> { { "pronoun", "Mrs" } }));
238+
}
239+
}

0 commit comments

Comments
 (0)