xamarin-macios/tests/xtro-sharpie/EnumCheck.cs

329 строки
10 KiB
C#
Исходник Обычный вид История

using System;
2016-05-26 16:06:52 +03:00
using System.Collections.Generic;
using Mono.Cecil;
using Clang.Ast;
namespace Extrospection {
class EnumCheck : BaseVisitor {
Dictionary<string,TypeDefinition> enums = new Dictionary<string, TypeDefinition> (StringComparer.InvariantCultureIgnoreCase);
Dictionary<string,TypeDefinition> obsoleted_enums = new Dictionary<string,TypeDefinition> ();
Dictionary<long, FieldDefinition> managed_signed_values = new Dictionary<long, FieldDefinition> ();
Dictionary<ulong, FieldDefinition> managed_unsigned_values = new Dictionary<ulong, FieldDefinition> ();
Dictionary<long, (string Name, bool Deprecated)> native_signed_values = new Dictionary<long, (string Name, bool Deprecated)> ();
Dictionary<ulong, (string Name, bool Deprecated)> native_unsigned_values = new Dictionary<ulong,(string Name, bool Deprecated)> ();
2016-05-26 16:06:52 +03:00
public override void VisitManagedType (TypeDefinition type)
{
// exclude non enum and nested enums, e.g. bunch of Selector enums in CTFont
if (!type.IsEnum || type.IsNested)
return;
var name = type.Name;
// exclude obsolete enums, presumably we already know there's something wrong with them if they've been obsoleted.
if (type.IsObsolete ()) {
obsoleted_enums [name] = type;
return;
}
if (AttributeHelpers.HasAnyObsoleted (type)) {
obsoleted_enums [name] = type;
return;
}
2016-05-26 16:06:52 +03:00
// e.g. WatchKit.WKErrorCode and WebKit.WKErrorCode :-(
if (!enums.TryGetValue (name, out var td))
2016-05-26 16:06:52 +03:00
enums.Add (name, type);
else {
var (t1, t2) = Helpers.Sort (type, td);
if (t1.Namespace.StartsWith ("OpenTK.", StringComparison.Ordinal)) {
// OpenTK duplicate a lots of enums between it's versions
} else if (t1.IsNotPublic && String.IsNullOrEmpty (t1.Namespace)) {
// ignore special, non exposed types
} else {
var framework = Helpers.GetFramework (t1);
Log.On (framework).Add ($"!duplicate-type-name! {name} enum exists as both {t1.FullName} and {t2.FullName}");
}
2016-05-26 16:06:52 +03:00
}
}
public override void VisitEnumDecl (EnumDecl decl, VisitKind visitKind)
{
if (visitKind != VisitKind.Enter)
return;
if (!decl.IsDefinition)
return;
string name = decl.Name;
if (name == null)
return;
// check availability macros to see if the API is available on the OS and not deprecated
if (!decl.IsAvailable ())
return;
var framework = Helpers.GetFramework (decl);
if (framework == null)
return;
2016-05-26 16:06:52 +03:00
var mname = Helpers.GetManagedName (name);
// If our enum is obsoleted, then don't process it.
if (obsoleted_enums.ContainsKey (mname))
return;
if (!enums.TryGetValue (mname, out var type)) {
if (!decl.IsDeprecated ()) // don't report deprecated enums as unbound
Log.On (framework).Add ($"!missing-enum! {name} not bound");
2016-05-26 16:06:52 +03:00
return;
} else
enums.Remove (mname);
int native_size = 4;
bool native = false;
// FIXME: this can be simplified
switch (decl.IntegerQualType.ToString ()) {
case "NSInteger":
case "NSUInteger":
case "CFIndex":
case "CFOptionFlags":
case "AVAudioInteger":
2016-05-26 16:06:52 +03:00
native_size = 8; // in managed code it's always the largest size
native = true;
break;
case "unsigned long":
case "unsigned int":
case "int32_t":
case "uint32_t":
case "int":
case "GLint":
case "GLuint":
case "GLenum":
case "SInt32":
case "UInt32":
case "OptionBits": // UInt32
case "long":
case "FourCharCode":
2017-09-06 05:38:02 +03:00
case "OSStatus":
2016-05-26 16:06:52 +03:00
break;
case "int64_t":
case "uint64_t":
case "unsigned long long":
case "CVOptionFlags": // uint64_t
2016-05-26 16:06:52 +03:00
native_size = 8;
break;
case "UInt16":
case "int16_t":
case "uint16_t":
case "short":
native_size = 2;
break;
case "UInt8":
case "int8_t":
case "uint8_t":
native_size = 1;
break;
default:
throw new NotImplementedException (decl.IntegerQualType.ToString ());
}
// check correct [Native] decoration
if (!decl.IsDeprecated ()) {
if (native) {
if (!IsNative (type))
Log.On (framework).Add ($"!missing-enum-native! {name}");
} else {
if (IsNative (type))
Log.On (framework).Add ($"!extra-enum-native! {name}");
}
2016-05-26 16:06:52 +03:00
}
int managed_size = 4;
bool signed = true;
2016-05-26 16:06:52 +03:00
switch (GetEnumUnderlyingType (type).Name) {
case "Byte":
signed = false;
managed_size = 1;
break;
2016-05-26 16:06:52 +03:00
case "SByte":
managed_size = 1;
break;
case "Int16":
managed_size = 2;
break;
2016-05-26 16:06:52 +03:00
case "UInt16":
signed = false;
2016-05-26 16:06:52 +03:00
managed_size = 2;
break;
case "Int32":
break;
2016-05-26 16:06:52 +03:00
case "UInt32":
signed = false;
2016-05-26 16:06:52 +03:00
break;
case "Int64":
managed_size = 8;
break;
2016-05-26 16:06:52 +03:00
case "UInt64":
signed = false;
2016-05-26 16:06:52 +03:00
managed_size = 8;
break;
default:
throw new NotImplementedException ();
}
var fields = type.Fields;
if (signed) {
managed_signed_values.Clear ();
native_signed_values.Clear ();
foreach (var f in fields) {
// skip special `value__`
if (f.IsRuntimeSpecialName && !f.IsStatic)
continue;
if (!f.IsObsolete ())
managed_signed_values [Convert.ToInt64 (f.Constant)] = f;
}
long n = 0;
foreach (var value in decl.Values) {
if ((value.InitExpr != null) && value.InitExpr.EvaluateAsInt (decl.AstContext, out var integer))
n = integer.SExtValue;
native_signed_values [n] = new (value.ToString (), value.IsDeprecated ());
// assume, sequentially assigned (in case next `value.InitExpr` is null)
n++;
}
foreach (var value in native_signed_values.Keys) {
if (!managed_signed_values.ContainsKey (value)) {
if (!native_signed_values [value].Deprecated && !decl.IsDeprecated ())
Log.On (framework).Add ($"!missing-enum-value! {type.Name} native value {native_signed_values [value].Name} = {value} not bound");
} else
managed_signed_values.Remove (value);
}
foreach (var value in managed_signed_values.Keys) {
if ((value == 0) && IsExtraZeroValid (type.Name, managed_signed_values [0].Name))
continue;
// value could be decorated with `[No*]` and those should not be reported
if (managed_signed_values [value].IsAvailable ())
Log.On (framework).Add ($"!extra-enum-value! Managed value {value} for {type.Name}.{managed_signed_values [value].Name} not found in native headers");
}
} else {
managed_unsigned_values.Clear ();
native_unsigned_values.Clear ();
foreach (var f in fields) {
// skip special `value__`
if (f.IsRuntimeSpecialName && !f.IsStatic)
continue;
if (!f.IsObsolete ())
managed_unsigned_values [Convert.ToUInt64 (f.Constant)] = f;
}
ulong n = 0;
foreach (var value in decl.Values) {
if ((value.InitExpr != null) && value.InitExpr.EvaluateAsInt (decl.AstContext, out var integer))
n = integer.ZExtValue;
native_unsigned_values [n] = new (value.ToString (), value.IsDeprecated ());
// assume, sequentially assigned (in case next `value.InitExpr` is null)
n++;
}
foreach (var value in native_unsigned_values.Keys) {
if (!managed_unsigned_values.ContainsKey (value)) {
// only for unsigned (flags) native enums we allow all bits set on 32 bits (UInt32.MaxValue)
// to be equal to all bit set on 64 bits (UInt64.MaxValue) since the MaxValue differs between
// 32bits (e.g. watchOS) and 64bits (all others) platforms
var log = true;
if (native && (value == UInt32.MaxValue)) {
log = !managed_unsigned_values.ContainsKey (UInt64.MaxValue);
managed_unsigned_values.Remove (UInt64.MaxValue);
}
if (log)
log &= !native_unsigned_values [value].Deprecated && !decl.IsDeprecated ();
if (log)
Log.On (framework).Add ($"!missing-enum-value! {type.Name} native value {native_unsigned_values [value].Name} = {value} not bound");
} else
managed_unsigned_values.Remove (value);
}
foreach (var value in managed_unsigned_values.Keys) {
if ((value == 0) && IsExtraZeroValid (type.Name, managed_unsigned_values [0].Name))
continue;
// value could be decorated with `[No*]` and those should not be reported
if (managed_unsigned_values [value].IsAvailable ())
Log.On (framework).Add ($"!extra-enum-value! Managed value {value} for {type.Name}.{managed_unsigned_values [value].Name} not found in native headers");
}
}
if (native_size != managed_size && !decl.IsDeprecated ())
Log.On (framework).Add ($"!wrong-enum-size! {name} managed {managed_size} vs native {native_size}");
2016-05-26 16:06:52 +03:00
}
static bool IsExtraZeroValid (string typeName, string valueName)
{
switch (valueName) {
// we often add `None = 0` to flags, when none exists
case "None":
return true;
// `Ok = 0` and `Success = 0` are often added to errors enums
case "Ok":
case "Success":
if (typeName.EndsWith ("ErrorCode", StringComparison.Ordinal))
return true;
if (typeName.EndsWith ("Status", StringComparison.Ordinal))
return true;
break;
// used in HealthKit for a default value
case "NotApplicable":
return typeName.StartsWith ("HK", StringComparison.Ordinal);
}
return false;
}
2016-05-26 16:06:52 +03:00
static bool IsNative (TypeDefinition type)
{
if (!type.HasCustomAttributes)
return false;
foreach (var ca in type.CustomAttributes) {
if (ca.Constructor.DeclaringType.Name == "NativeAttribute")
return true;
}
return false;
}
public static TypeReference GetEnumUnderlyingType (TypeDefinition self)
{
var fields = self.Fields;
for (int i = 0; i < fields.Count; i++) {
var field = fields [i];
if (!field.IsStatic)
return field.FieldType;
}
throw new ArgumentException ();
}
public override void End ()
{
// report any [Native] decorated enum for which we could not find a match in the header files
// e.g. a typo in the name
foreach (var extra in enums) {
var t = extra.Value;
if (obsoleted_enums.ContainsKey (t.Name))
continue;
if (!IsNative (t))
continue;
var framework = Helpers.GetFramework (t);
Log.On (framework).Add ($"!unknown-native-enum! {extra.Key} bound");
2016-05-26 16:06:52 +03:00
}
}
}
}