Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Harden GenAPI #4585

Merged
merged 27 commits into from
Apr 2, 2020
Merged

Harden GenAPI #4585

merged 27 commits into from
Apr 2, 2020

Conversation

dougbu
Copy link
Member

@dougbu dougbu commented Jan 3, 2020

(probably easiest to review individual commits)

@dougbu
Copy link
Member Author

dougbu commented Jan 3, 2020

/fyi @pakrym @JunTaoLuo

@dougbu
Copy link
Member Author

dougbu commented Jan 7, 2020

ping @safern

@ericstj @jaredpar is this ready to go?

@ericstj
Copy link
Member

ericstj commented Jan 7, 2020

I'll take another full look.

@dougbu can you make sure you test this package in dotnet/runtime repo before committing? We need to test 1) library build, 2) /t:GenerateReferenceSource from a couple projects: I'd suggest System.Runtime, System.Text.Json, and a few others. @safern do you think you can help provide some suggestions for testing?

@jaredpar
Copy link
Member

jaredpar commented Jan 7, 2020

@dougbu

The impact of the change on reference assemblies looks good to me and lines up with what I'd expect in a reference assembly. I'm not very familiar with this project though hence I'm hesitant to "sign off" on the totality of the changes.

@safern
Copy link
Member

safern commented Jan 7, 2020

@safern do you think you can help provide some suggestions for testing?

Those assemblies would be good tests.

Also, after those changes running APICompat on those projects if there are significant changes we need to take on those reference assemblies.

When I did the nullable changes, I also had a dummy assembly with a lot of edge cases and ran it against that.

if (!method.IsExplicitInterfaceMethod()) WriteVisibility(method.Visibility);
if (method.IsMethodUnsafe())
{
WriteKeyword("unsafe");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me like WriteInterfaceMethodModifiers is supposed to write modifiers that are common to both methods and interfaces methods. Can you instead just move unsafe check out of WriteMethodModifiers and into WriteInterfaceMethodModifiers? Granted this would change the ordering of the modifiers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, can't do this because WriteMethodModifiers(...) is also called to generate the modifiers for events and properties. Both can be declared unsafe.

src/Microsoft.DotNet.GenAPI/Program.cs Show resolved Hide resolved
Copy link
Member Author

@dougbu dougbu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few questions for you @ericstj

Otherwise, I'm working on your suggestions. Will also retest AspNetCore and the runtime projects you and @safern pointed me to. (My first reason to clone dotnet/runtime.)

@dougbu
Copy link
Member Author

dougbu commented Feb 17, 2020

FYI I'm nearly ready to update this PR to resolve the conflict, address review comments, and fix a couple of problems hit in dotnet/aspnetcore. That repo (with changes to use --respect-internals and so on) now generates clean ref/ code and passes ApiCompat checks in both directions with no errors. I'm testing in dotnet/runtime now…

When I did the nullable changes, I also had a dummy assembly with a lot of edge cases and ran it against that.

This was a good idea but not something I did in this case. The dotnet/aspnetcore repo contains all of the edge cases I was trying to fix i.e. I wasn't adding support for new C# features. Of course, please let me know of any corner cases I should double-check; hopefully I can find them where I've already tested.

@dougbu
Copy link
Member Author

dougbu commented Feb 18, 2020

Generating a private constructor when no other constructor exists doesn't always work in dotnet/runtime because some ref/ code calls filtered-out internal constructors. Which of the following do you prefer?

  1. Add a command-line option controlling the "default" generated constructor, switching it between private and internal
  2. Base the choice between private and internal on --respect-internals
  3. Base the choice between private and internal on whether a filtered-out internal constructor exists i.e. the code could check the constructor collection again in WritePrivateConstructor(...) (without the filter)
  4. Add a list of internal constructors to the *.Manual.cs files. This list shouldn't be incredibly long; I hit is inaccessible due to its protection level problems in only a few cases e.g. for the JsonNode() constructor
  5. Add a UnionFilter that prevents exclusion of a few docIDs from generation as well as a corresponding command-line switch then use that in dotnet/runtime e.g. through $(GenAPIAdditionalParameters)

When testing with dotnet msbuild /t:GenerateReferenceSource or msbuild /t:GenerateReferenceSource in the dotnet/runtime repo, GenApi emits errors about resolving 'System.Private.CoreLib` in a few src directories, including src\libraries\System.Runtime\src\ e.g.

  Unable to resolve assembly 'Assembly(Name=System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e)' referenced by 'Assembly(Name=System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)'.

The above occurs despite having built the libraries subcategory completely before starting. What am I missing?


Separately from the above, I see some differences in the generation for ExpandoObject:

-        bool System.Collections.Generic.IDictionary<string, object?>.TryGetValue(string key, out object? value) { throw null; }
-        System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, object?>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>.GetEnumerator() { throw null; }
+        bool System.Collections.Generic.IDictionary<string, object?>.TryGetValue(string key, out object value) { throw null; }
+        System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, object>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>.GetEnumerator() { throw null; }

Unlike other cases where the nullability generation has changed, the above code was correct before I ran GenApi. Before I get deep into debugging the change, is this a known issue that you've been working around?

@dougbu dougbu force-pushed the dougbu/fixup.genapi.4488 branch from faadc8a to 38fdbce Compare February 18, 2020 00:50
@dougbu
Copy link
Member Author

dougbu commented Feb 18, 2020

🆙📅 to current state of my branch.

My updated ref/ code for dotnet/runtime is visible at https://github.com/dotnet/runtime/compare/master...dougbu:dougbu/local.GenApi?expand=1

}

public static bool IsConstructorVisible(this ITypeDefinition type)
public static bool IsConstructorVisibleToFriendAssemblies(this ITypeDefinition type)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is new

return MemberHelper.IsVisibleOutsideAssembly(member);
}

public static bool IsVisibleToFriendAssemblies(this ITypeDefinitionMember member)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike most of this file (which was moved from TypeExtensions), this method and the local IsExplicitImplementationVisible(...) are new

@@ -339,7 +354,12 @@ private void WritePrivateConstructor(ITypeDefinition type)
type.IsStatic)
return;

WriteVisibility(TypeMemberVisibility.Assembly);
WriteVisibility(TypeMemberVisibility.Private);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change needs to be optional in some fashion. See overall comments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replied. Can you please record the reasoning for the WritePrivateConstructor in a comment when making your change?

@@ -32,7 +32,7 @@
<ApiCompatBaseline Condition="!Exists('$(ApiCompatBaseline)')">$(MSBuildProjectDirectory)\ApiCompatBaseline.txt</ApiCompatBaseline>

<MatchingRefApiCompatBaseline Condition="!Exists('$(MatchingRefApiCompatBaseline)')">$(MSBuildProjectDirectory)\MatchingRefApiCompatBaseline.$(_apiCompatTargetSuffix).txt</MatchingRefApiCompatBaseline>
<MatchingRefApiCompatBaseline Condition="'$(BaselineAllMatchingRefApiCompatError)' != 'true' and !Exists('$(MatchingRefApiCompatBaseline)')">$(MSBuildProjectDirectory)\MatchingRefApiCompatBaseline.txt</MatchingRefApiCompatBaseline>
<MatchingRefApiCompatBaseline Condition="!Exists('$(MatchingRefApiCompatBaseline)')">$(MSBuildProjectDirectory)\MatchingRefApiCompatBaseline.txt</MatchingRefApiCompatBaseline>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in commit comments, the previous Condition meant baselines were read and written from different files by default

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this was done to make the default behavior to write to TargetFramework-specific baseline files, though I am OK with this change.

@@ -68,8 +68,8 @@
<ApiCompatArgs Condition="'$(ApiCompatExcludeAttributeList)' != ''">$(ApiCompatArgs) --exclude-attributes "$(ApiCompatExcludeAttributeList)"</ApiCompatArgs>
<ApiCompatArgs Condition="'$(ApiCompatEnforceOptionalRules)' == 'true'">$(ApiCompatArgs) --enforce-optional-rules</ApiCompatArgs>
<ApiCompatArgs Condition="'$(BaselineAllAPICompatError)' != 'true' and Exists('$(ApiCompatBaseline)')">$(ApiCompatArgs) --baseline "$(ApiCompatBaseline)"</ApiCompatArgs>
<!-- Must be last option. -->
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a lie

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably was true before c380971.

excludeNonBrowsable.HasValue(), remapFile.Value(), !skipGroupByAssembly.HasValue(), leftOperandValue, rightOperandValue, excludeAttributes.Value());
var includeInternals = respectInternals.HasValue() &&
contractAssemblies.Any(assembly => assembly.Attributes.HasAttributeOfType(
"System.Runtime.CompilerServices.InternalsVisibleToAttribute"));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No const here because the string is used only in this one place. Any objections?

@ericstj
Copy link
Member

ericstj commented Feb 21, 2020

  1. Base the choice between private and internal on --respect-internals

I'm OK with this.

GenApi emits errors about resolving 'System.Private.CoreLib`

Do you see System.Private.CoreLib being passed in to GenAPI? If not, try adding DependsOnTargets="ResolveReferences" here: https://github.com/dotnet/runtime/blob/86a086377bd63cbe678b6cba5498e7ab4fd9b57d/src/libraries/Directory.Build.targets#L117
Not entirely sure why it doesn't have it /cc @safern @Anipik

I see some differences in the generation for ExpandoObject

Yes, we're working around nullable annotations on explicit interface implementations. #4722

@pgrawehr
Copy link

pgrawehr commented Mar 5, 2020

Could somebody please take a look at the related PR #4649? I think it's an easy but important fix.

@ericstj
Copy link
Member

ericstj commented Mar 5, 2020

@dougbu let me know if you're waiting on anything here. I can take another look if you have specific places for me to check. I was under the impression you were going to make some changes.

dougbu added 15 commits April 1, 2020 17:30
- split member-related methods into `MemberExtensions`
- add `MemberExtensions.IsVisibleToFriendAssemblies(...)`
- add `TypeExtensions.IsConstructorVisibleToFriendAssemblies(...)`
- remove parameter `null` checks
- remove `ContainingTypeDefinition` visibility checks from `Include(ITypeDefinitionMember)`
nit: remove and sort `using`s
- correct `$(MatchingRefApiCompatBaseline)` setting; read and write same file

nits:
- correct spelling of `$(ImplementationAssemblyAsContract)` property
- remove incorrect "must be last option" comments
- lack of comparer led to "X != X" messages due to varying `InternedKey` and similar properties
….) generates

- choose `private` if internal constructurs are likely to be generated by default
- flatten `IntersectionFilter.Filters` list to make `WritePrivateConstructor(...)` use reasonable
  - also improves overall efficiency
- aligns with generation exclusions in GenAPI (if enabled)
- otherwise, `ValidateApiCompatForSrc` target succeeds while `RunMatchingRefApiCompat` fails in some cases
  - or visa versa
- remove redundant bits
@dougbu dougbu force-pushed the dougbu/fixup.genapi.4488 branch from 3f3138c to 24d6981 Compare April 2, 2020 00:30
@dougbu
Copy link
Member Author

dougbu commented Apr 2, 2020

FYI the last update included only the $(IntermediateOutputPath) removal and a final rebase. Will merge when the checks complete and I've run local tests in dotnet/aspnetcore and dotnet/runtime.

@dougbu dougbu merged commit d6dd7a0 into master Apr 2, 2020
@dougbu dougbu deleted the dougbu/fixup.genapi.4488 branch April 2, 2020 04:59
dougbu added a commit that referenced this pull request Apr 16, 2020
- example failure in https://dev.azure.com/dnceng/internal/_build/results?buildId=601199
- undo a small part of #4585 (@1faec9a821cf767d349c7ff18c544876be28cf2d - unfortunately likely to be GC'd soon)
  - restore generation of `[StructLayout]` attributes on most value types
dougbu added a commit that referenced this pull request Apr 16, 2020
- example failure in https://dev.azure.com/dnceng/internal/_build/results?buildId=601199
- undo a small part of #4585)
  - restore generation of `[StructLayout]` attributes on most value types
  - original commit was 1faec9a
dougbu added a commit that referenced this pull request Apr 16, 2020
- example failure in https://dev.azure.com/dnceng/internal/_build/results?buildId=601199
- undo a small part of #4585)
  - restore generation of `[StructLayout]` attributes on most value types
  - original commit was 1faec9a
dougbu added a commit that referenced this pull request Apr 29, 2020
- #5361
- pass property value in `--impl-dirs` argument in `ValidateApiCompatForSrc` target
- pass property value in `----contract-depends` argument in `RunMatchingRefApiCompat` target
- reverts small part of #4585
  - specifically, 24d6981
dougbu added a commit that referenced this pull request Apr 29, 2020
- #5361
- pass property value in `--impl-dirs` argument in `ValidateApiCompatForSrc` target
- reverts small part of #4585
  - specifically, restore one line changed in 24d6981
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants