예제 #1
0
    public async Task PlatformContext_Event()
    {
        var source = @"
            using System;
            using System.Runtime.Versioning;
            namespace System
            {
                public class TheClass
                {
                    [SupportedOSPlatform(""ios"")]
                    public event EventHandler TheEvent;
                }
            }
        ";

        var expectedPlatforms = @"
            The API is only supported on these platforms:
            - iOS
        ";

        var catalog = await new FluentCatalogBuilder()
                      .AddFramework("net45", fx =>
                                    fx.AddAssembly("System.TheAssembly", source))
                      .BuildAsync();

        var availabilityContext = ApiAvailabilityContext.Create(catalog);
        var context             = PlatformAnnotationContext.Create(availabilityContext, "net45");
        var api        = Assert.Single(catalog.GetAllApis().Where(a => a.GetFullName() == "System.TheClass.TheEvent.TheEvent.add"));
        var annotation = context.GetPlatformAnnotation(api);

        Assert.True(annotation.IsSupported("ios"));
        Assert.False(annotation.IsSupported("windows"));

        Assert.Equal(expectedPlatforms.Unindent(), annotation.ToString().Trim());
    }
예제 #2
0
    public async Task InvalidateAsync()
    {
        if (!_environment.IsDevelopment())
        {
            File.Delete(GetDatabasePath());
            File.Delete(GetSuffixTreePath());
        }

        var azureConnectionString = _configuration["AzureStorageConnectionString"];

        var databasePath = GetDatabasePath();

        if (!File.Exists(databasePath))
        {
            var blobClient = new BlobClient(azureConnectionString, "catalog", "apicatalog.dat");
            await blobClient.DownloadToAsync(databasePath);
        }

        var catalog = await ApiCatalogModel.LoadAsync(databasePath);

        var availabilityContext = ApiAvailabilityContext.Create(catalog);
        var apiByGuid           = catalog.GetAllApis().ToDictionary(a => a.Guid);

        var suffixTreePath = GetSuffixTreePath();

        if (!File.Exists(suffixTreePath))
        {
            // TODO: Ideally the underlying file format uses compression. This seems weird.
            var blobClient = new BlobClient(azureConnectionString, "catalog", "suffixtree.dat.deflate");
            using var blobStream = await blobClient.OpenReadAsync();

            using var deflateStream = new DeflateStream(blobStream, CompressionMode.Decompress);
            using var fileStream    = File.Create(suffixTreePath);
            await deflateStream.CopyToAsync(fileStream);
        }

        var suffixTree = SuffixTree.Load(suffixTreePath);

        var jobBlobClient = new BlobClient(azureConnectionString, "catalog", "job.json");

        using var jobStream = await jobBlobClient.OpenReadAsync();

        var jobInfo = await JsonSerializer.DeserializeAsync <CatalogJobInfo>(jobStream);

        _catalog             = catalog;
        _availabilityContext = availabilityContext;
        _statistics          = catalog.GetStatistics();
        _apiByGuid           = apiByGuid;
        _suffixTree          = suffixTree;
        _jobInfo             = jobInfo;
    }
예제 #3
0
    public async Task PlatformContext_DenyList()
    {
        var source = @"
            using System.Runtime.Versioning;
            namespace System
            {
                [UnsupportedOSPlatform(""ios"")]
                [UnsupportedOSPlatform(""tvos"")]
                public class TheClass { }
            }
        ";

        var expectedPlatforms = @"
            The API is supported on any platform except for:
            - iOS
            - Mac Catalyst
            - tvOS
        ";

        var catalog = await new FluentCatalogBuilder()
                      .AddFramework("net45", fx =>
                                    fx.AddAssembly("System.Runtime", GetOperatingSystemWithImpliedPlatform())
                                    .AddAssembly("System.TheAssembly", source))
                      .BuildAsync();

        var availabilityContext = ApiAvailabilityContext.Create(catalog);
        var context             = PlatformAnnotationContext.Create(availabilityContext, "net45");
        var api        = Assert.Single(catalog.GetAllApis().Where(a => a.GetFullName() == "System.TheClass"));
        var annotation = context.GetPlatformAnnotation(api);

        Assert.False(annotation.IsSupported("ios"));
        Assert.False(annotation.IsSupported("tvos"));
        Assert.False(annotation.IsSupported("maccatalyst"));
        Assert.True(annotation.IsSupported("windows"));

        Assert.Equal(expectedPlatforms.Unindent(), annotation.ToString().Trim());
    }
예제 #4
0
    public static void Run(ApiCatalogModel catalog,
                           IReadOnlyCollection <string> filePaths,
                           IReadOnlyList <NuGetFramework> frameworks,
                           bool analyzeObsoletion,
                           IReadOnlyList <string> platforms,
                           Action <AssemblyResult> resultReceiver)
    {
        var apiByGuid           = catalog.GetAllApis().ToDictionary(a => a.Guid);
        var availabilityContext = ApiAvailabilityContext.Create(catalog);

        var platformContexts = frameworks.Select(fx => PlatformAnnotationContext.Create(availabilityContext, fx.GetShortFolderName()))
                               .ToDictionary(pc => pc.Framework);

        foreach (var api in catalog.GetAllApis())
        {
            var forwardedApi = catalog.GetForwardedApi(api);
            if (forwardedApi is not null)
            {
                apiByGuid[api.Guid] = forwardedApi.Value;
            }
        }

        var apiAvailability = new ConcurrentDictionary <ApiModel, ApiAvailability>();

        var resultSink = new BlockingCollection <AssemblyResult>();

        var resultSinkTask = Task.Run(() =>
        {
            foreach (var result in resultSink.GetConsumingEnumerable())
            {
                resultReceiver(result);
            }
        });

        Parallel.ForEach(filePaths, filePath =>
        {
            using var env    = new HostEnvironment();
            var assembly     = env.LoadAssemblyFrom(filePath);
            var assemblyName = assembly is not null
                                ? assembly.Name.Value
                                : Path.GetFileName(filePath);
            if (assembly is null)
            {
                var result = new AssemblyResult(assemblyName, "Not a valid .NET assembly", Array.Empty <ApiResult>());
                resultSink.Add(result);
            }
            else
            {
                var assemblyTfm       = assembly.GetTargetFrameworkMoniker();
                var assemblyFramework = string.IsNullOrEmpty(assemblyTfm) ? null : NuGetFramework.Parse(assemblyTfm);

                var crawler = new AssemblyCrawler();
                crawler.Crawl(assembly);

                var crawlerResults = crawler.GetResults();

                var apiResults             = new List <ApiResult>();
                var frameworkResultBuilder = new List <FrameworkResult>(frameworks.Count);
                var platformResultBuilder  = new List <PlatformResult?>(platforms.Count);

                foreach (var apiKey in crawlerResults.Data.Keys)
                {
                    if (apiByGuid.TryGetValue(apiKey.Guid, out var api))
                    {
                        var availability = apiAvailability.GetOrAdd(api, a => availabilityContext.GetAvailability(a));

                        frameworkResultBuilder.Clear();

                        foreach (var framework in frameworks)
                        {
                            // Analyze availability

                            AvailabilityResult availabilityResult;

                            var infos = availability.Frameworks.Where(fx => fx.Framework == framework).ToArray();

                            // NOTE: There are APIs that exist in multiple places in-box, e.g. Microsoft.Windows.Themes.ListBoxChrome.
                            //       It doesn't really matter for our purposes. Either way, we'll pick the first one.
                            var info = infos.FirstOrDefault(i => i.IsInBox) ?? infos.FirstOrDefault(i => !i.IsInBox);

                            if (info is null)
                            {
                                availabilityResult = AvailabilityResult.Unavailable;
                            }
                            else if (info.IsInBox)
                            {
                                availabilityResult = AvailabilityResult.AvailableInBox;
                            }
                            else
                            {
                                availabilityResult = AvailabilityResult.AvailableInPackage(info.Package.Value);
                            }

                            // Analyze obsoletion

                            ObsoletionResult?obsoletionResult;

                            if (!analyzeObsoletion || info?.Declaration.Obsoletion is null)
                            {
                                obsoletionResult = null;
                            }
                            else
                            {
                                var compiledAgainstObsoleteApi = false;

                                if (assemblyFramework is not null)
                                {
                                    var compiledAvailability = availabilityContext.GetAvailability(api, assemblyFramework);
                                    if (compiledAvailability?.Declaration.Obsoletion is not null)
                                    {
                                        compiledAgainstObsoleteApi = true;
                                    }
                                }

                                if (compiledAgainstObsoleteApi)
                                {
                                    obsoletionResult = null;
                                }
                                else
                                {
                                    var o            = info.Declaration.Obsoletion.Value;
                                    obsoletionResult = new ObsoletionResult(o.Message, o.Url);
                                }
                            }

                            // Analyze platform support

                            platformResultBuilder.Clear();

                            if (info is null)
                            {
                                for (var i = 0; i < platforms.Count; i++)
                                {
                                    platformResultBuilder.Add(null);
                                }
                            }
                            else
                            {
                                var platformContext = platformContexts[framework];
                                foreach (var platform in platforms)
                                {
                                    var annotation     = platformContext.GetPlatformAnnotation(api);
                                    var isSupported    = annotation.IsSupported(platform);
                                    var platformResult = isSupported ? PlatformResult.Supported : PlatformResult.Unsupported;
                                    platformResultBuilder.Add(platformResult);
                                }
                            }

                            var frameworkResult = new FrameworkResult(availabilityResult, obsoletionResult, platformResultBuilder.ToArray());
                            frameworkResultBuilder.Add(frameworkResult);
                        }

                        var apiResult = new ApiResult(api, frameworkResultBuilder.ToArray());
                        apiResults.Add(apiResult);
                    }
                }

                var results = new AssemblyResult(assemblyName, null, apiResults.ToArray());
                resultSink.Add(results);
            }
        });

        resultSink.CompleteAdding();
        resultSinkTask.Wait();
    }
예제 #5
0
        public async Task RunAsync(Func <AnalyzeResult, Task> receiver)
        {
            var framework = NuGetFramework.ParseFolder(_tfmSelector.DetermineTargetTfmValue());
            var platforms = _options.Platform.Select(p => p.ToString().ToLowerInvariant()).ToImmutableList();

            async void Receiver(AssemblyResult result)
            {
                _logger.LogInformation(Resources.BinaryAnalysisProcessedAssemblyMessageFormat, result.AssemblyName);

                foreach (var r in CreateUaResults(result).ToList())
                {
                    await receiver(r).ConfigureAwait(false);
                }
            }

            var catalog = await CatalogService.LoadCatalogAsync().ConfigureAwait(false);

            var apiByGuid           = catalog.GetAllApis().ToDictionary(a => a.UniqueId);
            var availabilityContext = ApiAvailabilityContext.Create(catalog);

            var platformContext = PlatformAnnotationContext.Create(availabilityContext, framework.GetShortFolderName());

            foreach (var api in catalog.GetAllApis())
            {
                var forwardedApi = catalog.GetForwardedApi(api);
                if (forwardedApi is not null)
                {
                    apiByGuid[api.UniqueId] = forwardedApi.Value;
                }
            }

            var apiAvailability = new ConcurrentDictionary <ApiModel, ApiAvailability>();

            using var resultSink = new BlockingCollection <AssemblyResult>();

            var resultSinkTask = Task.Run(() =>
            {
                foreach (var result in resultSink.GetConsumingEnumerable())
                {
                    Receiver(result);
                }
            });

            var filePaths = AssemblyFileSet.Create(_options.Content.ToList());

            Parallel.ForEach(filePaths, filePath =>
            {
                using var env    = new HostEnvironment();
                var assembly     = env.LoadAssemblyFrom(filePath);
                var assemblyName = assembly is not null
                                    ? assembly.Name.Value
                                    : Path.GetFileName(filePath);
                if (assembly is null)
                {
                    var result = new AssemblyResult(assemblyName, Resources.BinaryAnalysisInvalidAssemblyMessage, Array.Empty <ApiResult>());
                    resultSink.Add(result);
                }
                else
                {
                    var assemblyTfm       = assembly.GetTargetFrameworkMoniker();
                    var assemblyFramework = string.IsNullOrEmpty(assemblyTfm) ? null : NuGetFramework.Parse(assemblyTfm);

                    var crawler = new AssemblyCrawler();
                    crawler.Crawl(assembly);

                    var crawlerResults = crawler.CreateResults();

                    var apiResults             = new List <ApiResult>();
                    var frameworkResultBuilder = new List <FrameworkResult>();
                    var platformResultBuilder  = new List <PlatformResult?>(platforms.Count);

                    foreach (var apiKey in crawlerResults.Data.Keys)
                    {
                        if (apiByGuid.TryGetValue(apiKey.Id, out var api))
                        {
                            var availability = apiAvailability.GetOrAdd(api, a => availabilityContext.GetAvailability(a));

                            frameworkResultBuilder.Clear();

                            AnalyzeAvailability(availability, framework, out AvailabilityResult availabilityResult, out ApiFrameworkAvailability? info);

                            var obsoletionResult = AnalyzeObsoletion(availabilityContext, assemblyFramework, api, info);

                            AnalyzePlatformSupport(platforms, platformContext, frameworkResultBuilder, platformResultBuilder !, api, framework !, availabilityResult, info, obsoletionResult);

                            var apiResult = new ApiResult(api, frameworkResultBuilder.ToArray());
                            apiResults.Add(apiResult);
                        }