Skip to content

Commit 8e2c939

Browse files
jonathanpeppersjonpryor
authored andcommitted
[tools] initial version of an MSBuild "fuzzer" (#3229)
The idea is that you can run this tool while having a device/emulator connected: tools\msbuild-fuzzer\bin\Debug\msbuild-fuzzer.exe When running the output looks something like: DesignTimeBuild RenameResource RemoveClass ChangePackageName Install Clean RemoveClass Install Starting: Intent { cmp=com.foo.a82008ffe87dd442d92c47658b392fd55/md52d9cf6333b8e95e8683a477bc589eda5.MainActivity } TouchRandomFile Build The process will stop if an MSBuild error occurs, and you should get a stacktrace and find MSBuild binlogs in: bin\TestDebug\temp\Fuzzer So far I have not found problems running this for a while on Mac. I have found what appears to be Windows Defender locking a file on Windows: Xamarin.Forms.targets(91,3): error MSB4018: The "XamlCTask" task failed unexpectedly. System.IO.IOException: The process cannot access the file 'obj\Release\90\HelloForms.Android.dll' because it is being used by another process. Server stack trace: at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost) at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share) at Mono.Cecil.ModuleDefinition.ReadModule(String fileName, ReaderParameters parameters) at Mono.Cecil.AssemblyDefinition.ReadAssembly(String fileName, ReaderParameters parameters) at Xamarin.Forms.Build.Tasks.XamlCTask.Execute(IList`1& thrownExceptions) at Xamarin.Forms.Build.Tasks.XamlTask.Execute() at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Object[]& outArgs) at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg) Exception rethrown at [0]: at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) at Microsoft.Build.Framework.ITask.Execute() at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute() at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext() [HelloForms.Android.csproj] `<XamlCTask/>` is part of Xamarin.Forms, so it isn't something we can address in Xamarin.Android.
1 parent 537e2fc commit 8e2c939

File tree

4 files changed

+374
-0
lines changed

4 files changed

+374
-0
lines changed

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/MainActivity.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ protected override void OnCreate (Bundle bundle)
2828
button.Click += delegate {
2929
button.Text = string.Format ("{0} clicks!", count++);
3030
};
31+
32+
//${AFTER_ONCREATE}
3133
}
3234
}
3335
}

tools/msbuild-fuzzer/Program.cs

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading;
8+
using Xamarin.ProjectTools;
9+
10+
namespace MSBuild.Fuzzer
11+
{
12+
class Program
13+
{
14+
static readonly Random random = new Random ();
15+
static readonly List<string> installedPackages = new List<string> ();
16+
static ProjectBuilder builder;
17+
static XamarinFormsAndroidApplicationProject application;
18+
static bool needsInstall = true;
19+
static string directory;
20+
21+
static void Main ()
22+
{
23+
var temp = Path.Combine (Path.GetDirectoryName (typeof (Program).Assembly.Location));
24+
using (builder = new ProjectBuilder (Path.Combine ("temp", "Fuzzer")) {
25+
AutomaticNuGetRestore = false,
26+
CleanupAfterSuccessfulBuild = false,
27+
CleanupOnDispose = true,
28+
Root = Xamarin.Android.Build.Paths.TestOutputDirectory,
29+
}) {
30+
directory = Path.GetFullPath (Path.Combine (builder.Root, builder.ProjectDirectory));
31+
if (Directory.Exists (directory))
32+
Directory.Delete (directory, recursive: true);
33+
34+
application = new XamarinFormsAndroidApplicationProject ();
35+
application.AndroidManifest = application.AndroidManifest.Replace ("<uses-sdk />", "<uses-sdk android:targetSdkVersion=\"28\" />");
36+
application.MainActivity = application.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", "Android.Util.Log.Debug (\"FUZZER\", \"App started!\");");
37+
var abis = new string [] { "armeabi-v7a", "arm64-v8a", "x86" };
38+
application.SetProperty (KnownProperties.AndroidSupportedAbis, string.Join (";", abis));
39+
40+
if (!NuGetRestore ()) {
41+
Console.WriteLine ("Initial NuGet restore failed!");
42+
return;
43+
}
44+
45+
Func<bool> [] operations = {
46+
AddClass,
47+
AddResource,
48+
Build,
49+
ChangePackageName,
50+
Clean,
51+
DesignerBuild,
52+
DesignTimeBuild,
53+
Install,
54+
NuGetRestore,
55+
RemoveClass,
56+
RemoveResource,
57+
RenameClass,
58+
RenameResource,
59+
Run,
60+
TouchRandomFile,
61+
Uninstall,
62+
};
63+
64+
while (true) {
65+
var operation = operations [random.Next (operations.Length)];
66+
if (!operation ()) {
67+
break;
68+
}
69+
}
70+
}
71+
72+
Console.WriteLine ("Press enter to exit...");
73+
Console.ReadLine ();
74+
}
75+
76+
static bool NuGetRestore ()
77+
{
78+
Console.WriteLine (nameof (NuGetRestore));
79+
return builder.RunTarget (application, "Restore", doNotCleanupOnUpdate: true);
80+
}
81+
82+
static bool Build ()
83+
{
84+
Console.WriteLine (nameof (Build));
85+
return builder.Build (application, doNotCleanupOnUpdate: true);
86+
}
87+
88+
static bool DesignTimeBuild ()
89+
{
90+
Console.WriteLine (nameof (DesignTimeBuild));
91+
return builder.DesignTimeBuild (application, doNotCleanupOnUpdate: true);
92+
}
93+
94+
static readonly string [] DesignerParameters = new [] { "DesignTimeBuild=True", "AndroidUseManagedDesignTimeResourceGenerator=False" };
95+
96+
static bool DesignerBuild ()
97+
{
98+
Console.WriteLine (nameof (DesignerBuild));
99+
return builder.RunTarget (application, "SetupDependenciesForDesigner", doNotCleanupOnUpdate: true, parameters: DesignerParameters);
100+
}
101+
102+
static bool Clean ()
103+
{
104+
Console.WriteLine (nameof (Clean));
105+
return builder.Clean (application, doNotCleanupOnUpdate: true);
106+
}
107+
108+
static bool Install ()
109+
{
110+
Console.WriteLine (nameof (Install));
111+
if (!builder.Install (application, doNotCleanupOnUpdate: true)) {
112+
return false;
113+
}
114+
if (!installedPackages.Contains (application.PackageName))
115+
installedPackages.Add (application.PackageName);
116+
needsInstall = false;
117+
return true;
118+
}
119+
120+
static bool Uninstall ()
121+
{
122+
Console.WriteLine (nameof (Uninstall));
123+
foreach (var packageName in installedPackages) {
124+
Adb ($"uninstall {packageName}");
125+
}
126+
installedPackages.Clear ();
127+
needsInstall = true;
128+
return true;
129+
}
130+
131+
static bool Run ()
132+
{
133+
if (needsInstall && !Install ())
134+
return false;
135+
Console.WriteLine (nameof (Run));
136+
string stop = Adb ($"shell am force-stop {application.PackageName}", ignoreExitCode: true);
137+
Adb ("logcat -c");
138+
string activity = $"{application.PackageName}/md52d9cf6333b8e95e8683a477bc589eda5.MainActivity";
139+
string start = Adb ($"shell am start -n {activity}");
140+
Console.WriteLine (start.Trim ());
141+
//Wait for the app to start
142+
Thread.Sleep (3000);
143+
string logcat = Adb ("logcat -d");
144+
if (!logcat.Contains ("App started!")) {
145+
Console.WriteLine (logcat);
146+
throw new Exception ($"Activity {activity} did not start!");
147+
}
148+
return true;
149+
}
150+
151+
static string Adb (string arguments, bool ignoreExitCode = false)
152+
{
153+
var info = new ProcessStartInfo {
154+
FileName = "adb",
155+
Arguments = arguments,
156+
CreateNoWindow = true,
157+
WindowStyle = ProcessWindowStyle.Hidden,
158+
UseShellExecute = false,
159+
RedirectStandardError = true,
160+
RedirectStandardOutput = true,
161+
};
162+
var builder = new StringBuilder ();
163+
using (var process = new Process ()) {
164+
var stdout_done = new ManualResetEventSlim (false);
165+
var stderr_done = new ManualResetEventSlim (false);
166+
process.StartInfo = info;
167+
process.OutputDataReceived += (sender, e) => {
168+
if (e.Data != null) {
169+
builder.AppendLine (e.Data);
170+
} else {
171+
stdout_done.Set ();
172+
}
173+
};
174+
process.ErrorDataReceived += (sender, e) => {
175+
if (e.Data != null) {
176+
builder.AppendLine (e.Data);
177+
} else {
178+
stderr_done.Set ();
179+
}
180+
};
181+
process.Start ();
182+
process.BeginErrorReadLine ();
183+
process.BeginOutputReadLine ();
184+
process.WaitForExit ();
185+
stderr_done.Wait ();
186+
stdout_done.Wait ();
187+
if (!ignoreExitCode && process.ExitCode != 0) {
188+
Console.WriteLine (builder);
189+
throw new Exception ($"Adb exited with code: {process.ExitCode}");
190+
}
191+
}
192+
return builder.ToString ();
193+
}
194+
195+
static readonly string [] extensions = {
196+
".cs",
197+
".csproj",
198+
".png",
199+
".xaml",
200+
".xml",
201+
};
202+
203+
static bool TouchRandomFile ()
204+
{
205+
Console.WriteLine (nameof (TouchRandomFile));
206+
var files = (from f in Directory.EnumerateFiles (directory, "*", SearchOption.AllDirectories)
207+
let relative = f.Substring (directory.Length + 1)
208+
where !relative.StartsWith ("obj") && !relative.StartsWith ("bin")
209+
let ext = Path.GetExtension (f)
210+
where extensions.Contains (ext)
211+
select f).ToArray ();
212+
var file = files [random.Next (0, files.Length)];
213+
File.SetLastWriteTimeUtc (file, DateTime.UtcNow);
214+
File.SetLastAccessTimeUtc (file, DateTime.UtcNow);
215+
return true;
216+
}
217+
218+
static bool ChangePackageName ()
219+
{
220+
Console.WriteLine (nameof (ChangePackageName));
221+
application.PackageName = "com.foo.a" + RandomName ();
222+
application.Touch ("Properties\\AndroidManifest.xml");
223+
needsInstall = true;
224+
return true;
225+
}
226+
227+
static bool AddClass ()
228+
{
229+
Console.WriteLine (nameof (AddClass));
230+
application.Sources.Add (new Class ());
231+
return true;
232+
}
233+
234+
static bool RemoveClass ()
235+
{
236+
Console.WriteLine (nameof (RemoveClass));
237+
for (int i = application.Sources.Count - 1; i >= 0; i--) {
238+
if (application.Sources [i] is Class) {
239+
application.Sources.RemoveAt (i);
240+
break;
241+
}
242+
}
243+
return true;
244+
}
245+
246+
static bool RenameClass ()
247+
{
248+
Console.WriteLine (nameof (RemoveClass));
249+
var clazz = application.Sources.OfType<Class> ().FirstOrDefault ();
250+
if (clazz != null) {
251+
clazz.Rename ();
252+
}
253+
return true;
254+
}
255+
256+
static bool AddResource ()
257+
{
258+
Console.WriteLine (nameof (AddResource));
259+
application.Sources.Add (new AndroidResource ());
260+
return true;
261+
}
262+
263+
static bool RemoveResource ()
264+
{
265+
Console.WriteLine (nameof (RemoveResource));
266+
for (int i = application.Sources.Count - 1; i >= 0; i--) {
267+
if (application.Sources [i] is AndroidResource) {
268+
application.Sources.RemoveAt (i);
269+
break;
270+
}
271+
}
272+
return true;
273+
}
274+
275+
static bool RenameResource ()
276+
{
277+
Console.WriteLine (nameof (RenameResource));
278+
var resource = application.Sources.OfType<AndroidResource> ().FirstOrDefault ();
279+
if (resource != null) {
280+
resource.Rename ();
281+
}
282+
return true;
283+
}
284+
285+
static string RandomName () => Guid.NewGuid ().ToString ("N");
286+
287+
class Class : BuildItem.Source
288+
{
289+
public string TypeName { get; set; }
290+
291+
public Class () : base (RandomName () + ".cs")
292+
{
293+
Rename ();
294+
TextContent = () => $"public class Foo{TypeName} : Java.Lang.Object {{ }}";
295+
}
296+
297+
public void Rename ()
298+
{
299+
TypeName = RandomName ();
300+
Timestamp = null;
301+
}
302+
}
303+
304+
class AndroidResource : BuildItem
305+
{
306+
public string ResourceId { get; set; }
307+
308+
public string StringValue { get; set; }
309+
310+
public AndroidResource () : base ("AndroidResource", RandomName () + ".xml")
311+
{
312+
Rename ();
313+
TextContent = () => $@"<?xml version=""1.0"" encoding=""utf-8""?>
314+
<resources>
315+
<string name=""foo_{ResourceId}"">{StringValue}</string>
316+
</resources>";
317+
}
318+
319+
public void Rename ()
320+
{
321+
ResourceId = RandomName ();
322+
StringValue = RandomName ();
323+
Timestamp = null;
324+
}
325+
}
326+
}
327+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworks>net472</TargetFrameworks>
4+
<ProjectGuid>{A3671983-ABC2-4693-8872-463427B57117}</ProjectGuid>
5+
<OutputType>Exe</OutputType>
6+
</PropertyGroup>
7+
<Import Project="..\..\Configuration.props" />
8+
<ItemGroup>
9+
<ProjectReference Include="..\..\src\Xamarin.Android.Build.Tasks\Tests\Xamarin.ProjectTools\Xamarin.ProjectTools.csproj">
10+
<Project>{2dd1ee75-6d8d-4653-a800-0a24367f7f38}</Project>
11+
<Name>Xamarin.ProjectTools</Name>
12+
</ProjectReference>
13+
</ItemGroup>
14+
</Project>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.28803.352
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "msbuild-fuzzer", "msbuild-fuzzer.csproj", "{A3671983-ABC2-4693-8872-463427B57117}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.ProjectTools", "..\..\src\Xamarin.Android.Build.Tasks\Tests\Xamarin.ProjectTools\Xamarin.ProjectTools.csproj", "{2DD1EE75-6D8D-4653-A800-0A24367F7F38}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Release|Any CPU = Release|Any CPU
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{A3671983-ABC2-4693-8872-463427B57117}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{A3671983-ABC2-4693-8872-463427B57117}.Debug|Any CPU.Build.0 = Debug|Any CPU
18+
{A3671983-ABC2-4693-8872-463427B57117}.Release|Any CPU.ActiveCfg = Release|Any CPU
19+
{A3671983-ABC2-4693-8872-463427B57117}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|Any CPU.Build.0 = Release|Any CPU
24+
EndGlobalSection
25+
GlobalSection(SolutionProperties) = preSolution
26+
HideSolutionNode = FALSE
27+
EndGlobalSection
28+
GlobalSection(ExtensibilityGlobals) = postSolution
29+
SolutionGuid = {E88EED5D-9DFA-405D-9495-4EC9A146B249}
30+
EndGlobalSection
31+
EndGlobal

0 commit comments

Comments
 (0)