| File: CodeGen\PermissionSetAttribute.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; using System; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; namespace Microsoft.CodeAnalysis.CodeGen { /// <summary> /// This class represents the PermissionSetAttribute specified in source which needs fixup during codegen. /// </summary> /// <remarks> /// PermissionSetAttribute needs fixup when it contains an assignment to the 'File' property as a single named attribute argument. /// Fixup performed is ported from SecurityAttributes::FixUpPermissionSetAttribute at ndp\clr\src\vm\securityattributes.cpp. /// It involves following steps: /// 1) Verifying that the specified file name resolves to a valid path: This is done during binding. /// 2) Reading the contents of the file into a byte array. /// 3) Convert each byte in the file content into two bytes containing hexadecimal characters (see method <see cref="ConvertToHex"/>). /// 4) Replacing the 'File = fileName' named argument with 'Hex = hexFileContent' argument, where hexFileContent is the converted output from step 3) above. /// </remarks> internal class PermissionSetAttributeWithFileReference : Cci.ICustomAttribute { private readonly Cci.ICustomAttribute _sourceAttribute; private readonly string _resolvedPermissionSetFilePath; internal const string FilePropertyName = "File"; internal const string HexPropertyName = "Hex"; public PermissionSetAttributeWithFileReference(Cci.ICustomAttribute sourceAttribute, string resolvedPermissionSetFilePath) { RoslynDebug.Assert(resolvedPermissionSetFilePath != null); _sourceAttribute = sourceAttribute; _resolvedPermissionSetFilePath = resolvedPermissionSetFilePath; } /// <summary> /// Zero or more positional arguments for the attribute constructor. /// </summary> public ImmutableArray<Cci.IMetadataExpression> GetArguments(EmitContext context) { return _sourceAttribute.GetArguments(context); } /// <summary> /// A reference to the constructor that will be used to instantiate this custom attribute during execution (if the attribute is inspected via Reflection). /// </summary> public Cci.IMethodReference Constructor(EmitContext context, bool reportDiagnostics) => _sourceAttribute.Constructor(context, reportDiagnostics); /// <summary> /// Zero or more named arguments that specify values for fields and properties of the attribute. /// </summary> public ImmutableArray<Cci.IMetadataNamedArgument> GetNamedArguments(EmitContext context) { // Perform fixup Cci.ITypeReference stringType = context.Module.GetPlatformType(Cci.PlatformType.SystemString, context); #if DEBUG // Must have exactly 1 named argument. var namedArgs = _sourceAttribute.GetNamedArguments(context); Debug.Assert(namedArgs.Length == 1); // Named argument must be 'File' property of string type var fileArg = namedArgs.First(); Debug.Assert(fileArg.ArgumentName == FilePropertyName); Debug.Assert(context.Module.IsPlatformType(fileArg.Type, Cci.PlatformType.SystemString)); // Named argument value must be a non-empty string Debug.Assert(fileArg.ArgumentValue is MetadataConstant); var fileName = (string?)((MetadataConstant)fileArg.ArgumentValue).Value; Debug.Assert(!String.IsNullOrEmpty(fileName)); // PermissionSetAttribute type must have a writable public string type property member 'Hex' ISymbol iSymbol = _sourceAttribute.GetType(context).GetInternalSymbol()!.GetISymbol(); Debug.Assert(((INamedTypeSymbol)iSymbol).GetMembers(HexPropertyName).Any( member => member.Kind == SymbolKind.Property && ((IPropertySymbol)member).Type.SpecialType == SpecialType.System_String)); #endif string hexFileContent; // Read the file contents at the resolved file path into a byte array. // May throw PermissionSetFileReadException, which is handled in Compilation.Emit. var resolver = context.Module.CommonCompilation.Options.XmlReferenceResolver; // If the resolver isn't available we won't get here since we had to use it to resolve the path. RoslynDebug.Assert(resolver != null); try { using (Stream stream = resolver.OpenReadChecked(_resolvedPermissionSetFilePath)) { // Convert the byte array contents into a string in hexadecimal format. hexFileContent = ConvertToHex(stream); } } catch (IOException e) { throw new PermissionSetFileReadException(e.Message, _resolvedPermissionSetFilePath); } // Synthesize a named attribute argument "Hex = hexFileContent". return ImmutableArray.Create<Cci.IMetadataNamedArgument>(new HexPropertyMetadataNamedArgument(stringType, new MetadataConstant(stringType, hexFileContent))); } // internal for testing purposes. internal static string ConvertToHex(Stream stream) { RoslynDebug.Assert(stream != null); var pooledStrBuilder = PooledStringBuilder.GetInstance(); StringBuilder stringBuilder = pooledStrBuilder.Builder; int b; while ((b = stream.ReadByte()) >= 0) { stringBuilder.Append(ConvertHexToChar((b >> 4) & 0xf)); stringBuilder.Append(ConvertHexToChar(b & 0xf)); } return pooledStrBuilder.ToStringAndFree(); } private static char ConvertHexToChar(int b) { Debug.Assert(b < 0x10); return (char)(b < 10 ? '0' + b : 'a' + b - 10); } /// <summary> /// The number of positional arguments. /// </summary> public int ArgumentCount => _sourceAttribute.ArgumentCount; /// <summary> /// The number of named arguments. /// </summary> public ushort NamedArgumentCount { get { Debug.Assert(_sourceAttribute.NamedArgumentCount == 1); return 1; } } /// <summary> /// The type of the attribute. For example System.AttributeUsageAttribute. /// </summary> public Cci.ITypeReference GetType(EmitContext context) => _sourceAttribute.GetType(context); public bool AllowMultiple => _sourceAttribute.AllowMultiple; private readonly struct HexPropertyMetadataNamedArgument : Cci.IMetadataNamedArgument { private readonly Cci.ITypeReference _type; private readonly Cci.IMetadataExpression _value; public HexPropertyMetadataNamedArgument(Cci.ITypeReference type, Cci.IMetadataExpression value) { _type = type; _value = value; } public string ArgumentName { get { return HexPropertyName; } } public Cci.IMetadataExpression ArgumentValue { get { return _value; } } public bool IsField { get { return false; } } Cci.ITypeReference Cci.IMetadataExpression.Type { get { return _type; } } void Cci.IMetadataExpression.Dispatch(Cci.MetadataVisitor visitor) { visitor.Visit(this); } } } /// <summary> /// Exception class to enable generating ERR_PermissionSetAttributeFileReadError while reading the file for PermissionSetAttribute fixup. /// </summary> internal class PermissionSetFileReadException : Exception { private readonly string _file; public PermissionSetFileReadException(string message, string file) : base(message) { _file = file; } public string FileName => _file; public string PropertyName => PermissionSetAttributeWithFileReference.FilePropertyName; } }