Conversation
| return null; | ||
| } | ||
|
|
||
| public virtual ModuleDesc ResolveModule(AssemblyName name, bool throwIfNotFound = true) |
There was a problem hiding this comment.
If you want to add support for multi-module assemblies, it should be done in separate PR.
It would be nice to submit this in separate PR as well. #Resolved |
Modules in multimodule assemblies do not have assembly names. They are always resolved in the context of the owning assembly. #Resolved |
These are not the only internal APIs used by the ILVerify tests... #Resolved |
|
Updated the PR with Jan's feedback (removed the multi-module and auto-discovery features). Also, I'm thinking the |
VSadov
left a comment
There was a problem hiding this comment.
I don't have much to add. So LGTM.
I am fairly sure, though, that we will have to tweak the API as we get more information from the actual use.
Agree that these should be split. #Resolved |
src/ILVerify/src/Verifier.cs
Outdated
| continue; | ||
| } | ||
|
|
||
| var methodName = method.ToString(); |
There was a problem hiding this comment.
ToString() on the type system objects is a debugging helper, not an API. We reserve the right to change the output any time to aid debuggability. I recently overhauled it in #5065. #Resolved
|
I noticed that the tests were not executable any longer after these changes. This is caused by the method tester now resolving the test assemblies in a different way, which enforces assembly name == file name. Some older test assemblies did not respect this. I have submitted a PR fixing it: #5198 #Resolved |
src/ILVerify/src/Verifier.cs
Outdated
| return new VerificationResult(); | ||
| } | ||
|
|
||
| internal VerificationResult VerifyMethod(MethodDesc method, MethodIL methodIL, string moduleName) |
There was a problem hiding this comment.
Do these methods need to take moduleName? You should be always able to get the module name from MethodDesc. #Resolved
There was a problem hiding this comment.
One can also get the MethodDesc from the MethodIL.
There was a problem hiding this comment.
I should rename the moduleName. This parameter is used to maintain the old command-line output, which inclues file paths.
But when the verifier is used in-memory, I'm passing in the module name. #Closed
src/ILVerify/src/Verifier.cs
Outdated
| } | ||
| catch (VerificationException) | ||
| { | ||
| numErrors++; |
There was a problem hiding this comment.
VerificationExceptions are thrown to either abort the entire method or a single basic block. When such an event happens, the rule, which caused the abort, should already have reported an error, therefore I think increasing numErrors here is redundant.
It also breaks some test cases.
#Resolved
There was a problem hiding this comment.
Thanks.
It seems I've not been running all the tests locally and CI isn't either :-( Investing in test automation would surely help.
#Resolved
I agree. As discussed in the corresponding issue, it would make sense to me, to have: As far as the auto-discovery is concerned: Would it make sense to add a property |
src/ILVerify/src/Program.cs
Outdated
| private string _systemModule = DefaultSystemModuleName; | ||
| private Dictionary<string, string> _inputFilePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | ||
| private Dictionary<string, string> _referenceFilePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | ||
| private AssemblyName _systemModule; |
There was a problem hiding this comment.
Shouldn't this be set to AssemblyName("mscorlib") as a default?
#Closed
There was a problem hiding this comment.
We don't know if the caller is using simple names or full names as identifiers. #Closed
There was a problem hiding this comment.
Oops. You were right :-) #Closed
src/ILVerify/src/Verifier.cs
Outdated
| var result = VerifyMethod(method, methodIL, path); | ||
| if (result.NumErrors > 0) | ||
| { | ||
| return result; |
There was a problem hiding this comment.
Immediately returning here will abort the module verification after the first method has reported an error. This is different from how we and PEVerify previously handled this (keep verifying as long as sensible). #Resolved
src/ILVerify/src/Verifier.cs
Outdated
| public int NumErrors = 0; | ||
| public string Message = string.Empty; | ||
| internal IEnumerable<VerifierError> _errors; // Note: there may be fewer errors recorded here than counted in NumErrors, which also counts exceptions | ||
| } |
There was a problem hiding this comment.
I agree with @jkotas. I think it would make sense to simply have an enumeration of VerificationErrors (which can be an array internally).
I think a new VerificationError class would be helpful for this, which could look like this:
class VerificationError
{
public readonly string ModuleName;
public readonly string MethodName;
public readonly VerificationErrorArgs ErrorArgs;
}It might even make sense to use a MethodDefinitionHandle and PEReader instead of strings, to be more consistent with the proposed Verify methods.
I am currently working on a use case, where having the VerificationErrorArgs for each reported error is vital. Creating the error string can then also be implemented by the application using the Verifier rather than the Verifier having to implement it.
#Resolved
There was a problem hiding this comment.
I went with strings. Feel free to adjust to your need, after this PR but before we publish.
Just to confirm your proposal: the user of the API should be responsible for enumerating all method definitions that it cares about and verify them one by one (using a public API like |
Yes - when the user wants to filter which methods to verify.
The public API should be: MethodDesc is internal implementation detail. It should not show up in any public APIs. We should make sure that it is not marked as public in the final version of the .dll. #Resolved |
src/ILVerify/src/Verifier.cs
Outdated
| _typeSystemContext.SetSystemModule(_typeSystemContext.GetModule(name)); | ||
| } | ||
|
|
||
| public VerificationResult Verify(AssemblyName moduleToVerify) |
There was a problem hiding this comment.
I agree. As discussed in the corresponding issue, it would make sense to me, to have:
Verify(PEReader) for verifying entire modules
Verify(PEReader, MethodDefinitionHandle) for verifying a specific method
Verify(PEReader, TypeDefinitionHandle) for verifying a specific type
If the API accepts PEReader, then we have to give up caching in SimpleTypeSystemContext, since PEReaders can't be keys in a dictionary. New EcmaModules will be recreated multiple times.
I don't know if that is just a performance concern or if that might cause identity problems as well... #Closed
There was a problem hiding this comment.
since PEReaders can't be keys in a dictionary
Why not? We can use their object identity. #Closed
There was a problem hiding this comment.
The PEReaders returned by IResolver.Resolve are currently not cached, so we're getting new ones. I'll add caching then object identity should work. #Closed
|
Pushed a commit that addresses the feedback so far. |
We have all the scaffolding there. In #3725 the ILVerify project made the jump to netcoreapp2.0 before the repo was able to deal with it, so the build and testing got disconnected from how the rest of the repo is built. As you note, we don't run the tests in the CI, and we don't even build the project in the CI. At this point we should be able to hook it up into the build again and make whatever changes are necessary for it to build with |
src/ILVerify/src/IResolver.cs
Outdated
| return null; | ||
| } | ||
|
|
||
| public abstract PEReader ResolveCore(AssemblyName name); |
There was a problem hiding this comment.
This method is only meant to be called by the resolver itself, right? Would it make sense to make it protected then? #Resolved
src/ILVerify/src/Program.cs
Outdated
| var results = _verifier.Verify(peReader, methodHandle); | ||
| if (results.Count() > 0) | ||
| { | ||
| return results; |
There was a problem hiding this comment.
Immediately returning here will abort the verification of the entire assembly after the first method has reported an error, right? This is different from how we and PEVerify previously handled this (keep verifying as long as sensible).
It might make sense to build up a list of VerificationResults instead. #Resolved
| { | ||
| } | ||
| PEReader peReader = _resolver.Resolve(name); | ||
| string actualSimpleName =peReader.GetSimpleName(); |
There was a problem hiding this comment.
Nit: missing space after =. #Resolved
src/ILVerify/src/Verifier.cs
Outdated
|
|
||
| private SimpleTypeSystemContext _typeSystemContext; | ||
|
|
||
| private static VerificationResult s_noSystemModuleResult = new VerificationResult() { Message = "No system module specified" }; |
There was a problem hiding this comment.
Would it make sense to also throw an exception for this case instead? #Resolved
I totally agree. That's not your fault. |
|
@ArztSamuel Roslyn has some test infrastructure to compile IL on the fly (see "CompileIL"), if that helps. Takes a string as input (and maybe some references?) and produces an array of bytes. #Resolved |
|
|
||
| namespace ILVerify | ||
| { | ||
| class SimpleTypeSystemContext : MetadataTypeSystemContext |
There was a problem hiding this comment.
It would be nice to rename this to ILVerifyTypeSystemContext. It is becoming very IL-verify specific. #Resolved
There was a problem hiding this comment.
Sure. I'll rename as last thing (and also split into exe+dll) once review is ok, so that the diff remains readable. #Resolved
There was a problem hiding this comment.
and also split into exe+dll
I would do the split in separate PR.
#Resolved
| foreach (KeyValuePair<EcmaModule, ModuleData> entry in _moduleData) | ||
| EcmaModule module = CreateModule(peReader); | ||
| string simpleName = peReader.GetSimpleName(); | ||
| if (module is null && throwIfNotFound) |
There was a problem hiding this comment.
How can module be null here? #Resolved
| { | ||
| public class VerificationResult | ||
| { | ||
| public string ModuleName { get; internal set; } |
There was a problem hiding this comment.
Do we need ModuleName here? The verification granularity is on module, so it will be always same for the given invocation.
If we need it here, it should be PEReader that the caller gets the ModuleName from if it needs it. #Resolved
| { | ||
| public string ModuleName { get; internal set; } | ||
| public string TypeName { get; internal set; } | ||
| public string Method { get; internal set; } |
There was a problem hiding this comment.
This should be MethodDefinitionHandle . #Resolved
src/ILVerify/src/Verifier.cs
Outdated
|
|
||
| public IEnumerable<VerificationResult> Verify(PEReader peReader, TypeDefinitionHandle typeHandle) | ||
| { | ||
| if (peReader is null) |
There was a problem hiding this comment.
Some places use is null, other places use == null. It would be nice to be consistent. Personally, is null reads harder to me than == null. #Resolved
src/ILVerify/src/Verifier.cs
Outdated
|
|
||
| if (_typeSystemContext.SystemModule is null) | ||
| { | ||
| yield return s_noSystemModuleResult; |
There was a problem hiding this comment.
I do not think this should be verification error. It should just throw exception - it is invalid use. #Resolved
| { | ||
| // This is called once for every assembly that should be verified, so linear search is acceptable. | ||
| foreach (KeyValuePair<EcmaModule, ModuleData> entry in _moduleData) | ||
| string actualSimpleName = peReader.GetSimpleName(); |
There was a problem hiding this comment.
This will create MetadataReader and throw it away immediately. The GetModule method a few lines below will create the MetadataReader again.
It would be better to move this validation to after the GetModule call (and delete the GetSimpleName method because of it should not be needed anymore). #Resolved
| { | ||
| public class VerificationResult | ||
| { | ||
| public string TypeName { get; internal set; } |
There was a problem hiding this comment.
We should not need TypeName. It can be fetched from MethodDefinitionHandle. #Resolved
There was a problem hiding this comment.
Removed TypeName #Resolved
src/ILVerify/src/Program.cs
Outdated
| Write(" : "); | ||
| Write(result.TypeName); | ||
| Write("::"); | ||
| Write(result.Method); |
There was a problem hiding this comment.
I do not think this will print a nice name of the method. #Resolved
There was a problem hiding this comment.
Thanks
Fixed this and verified manually. Here's an example of output:
> ILVerify.exe D:\issues\Console46\Console46\bin\Debug\New.exe -r C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
[IL]: Error: [D:\issues\Console46\Console46\bin\Debug\New.exe : Program::set_Property(Internal.TypeSystem.Ecma.EcmaType)][offset 0x00000002] Fall through end of the method without returning.
1 Error(s) Verifying D:\issues\Console46\Console46\bin\Debug\New.exe
``` #Resolved
|
LGTM modulo comments |
src/ILVerify/src/Program.cs
Outdated
| } | ||
|
|
||
| message.Append(" "); | ||
| var numErrors = results.Count(); |
There was a problem hiding this comment.
I think since VerifyAssembly is implemented using yield return, this will verify the entire assembly a second time, just to get the error count. It might make sense to simply increase a counter in the above foreach instead. #Resolved
There was a problem hiding this comment.
Definitely. Thanks! #Resolved
| using System.Collections.Generic; | ||
| using System.Reflection; | ||
| using System.Reflection.Metadata; | ||
| using System.Reflection.PortableExecutable; |
There was a problem hiding this comment.
This line should not be necessary anymore.
src/ILVerify/src/Verifier.cs
Outdated
| _typeSystemContext.SetSystemModule(_typeSystemContext.GetModule(_typeSystemContext._resolver.Resolve(name))); | ||
| } | ||
|
|
||
| public EcmaModule GetModule(PEReader peReader) |
There was a problem hiding this comment.
EcmaModule should not referenced in the public surface of the eventual ILVerify library. Could you please make it internal? #Resolved
|
Could please fix the few remaining nits, and undo the strongname signing hacks? I think we can merge this, and then start new PR for the additional changes. |
…oject hacks for signing
|
Cool. Thanks a lot for the review and feedback. Also tagging @A-And as FYI since he's working on nuget package. |
@ArztSamuel @jkotas @VSadov Here is another stab at a public API for ILVerify.
The ILVerify program, the ILVerify tests and the Roslyn tests all rely on the same API, summarized below. There are two APIs that are internal and used by the ILVerify tests.
Note 1: there is auto-discovery feature for the system module. It is required for Roslyn tests which create their own core library, but the test framework doesn't know when that is the case. If this feature feels wrong, I can further explore a Roslyn-side solution.
Note 2: the
SimpleTypeSystemContextkeeps a cache ofEcmaModules (_modules). That is currently keyed on simple name instead of full name. That's because I don't know how to get a full name from anEcmaModuleorMetadataReader(seeCreateModulemethod). Any tips?If the API looks good, I will clean up this PR (removing strong name using Roslyn key, switch back to an exe).
Fixes #3734