|
// 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;
}
}
|