2021-08-11 11:06:46 +03:00
|
|
|
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 {
|
|
|
|
|
2017-12-15 22:08:09 +03:00
|
|
|
Dictionary<string,TypeDefinition> enums = new Dictionary<string, TypeDefinition> (StringComparer.InvariantCultureIgnoreCase);
|
2021-08-10 14:40:25 +03:00
|
|
|
Dictionary<string,TypeDefinition> obsoleted_enums = new Dictionary<string,TypeDefinition> ();
|
2020-10-05 22:57:18 +03:00
|
|
|
Dictionary<long, FieldDefinition> managed_signed_values = new Dictionary<long, FieldDefinition> ();
|
|
|
|
Dictionary<ulong, FieldDefinition> managed_unsigned_values = new Dictionary<ulong, FieldDefinition> ();
|
|
|
|
Dictionary<long, string> native_signed_values = new Dictionary<long, string> ();
|
|
|
|
Dictionary<ulong, string> native_unsigned_values = new Dictionary<ulong,string> ();
|
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;
|
|
|
|
|
2021-08-10 14:40:25 +03:00
|
|
|
var name = type.Name;
|
|
|
|
|
2019-08-14 18:46:55 +03:00
|
|
|
// exclude obsolete enums, presumably we already know there's something wrong with them if they've been obsoleted.
|
2021-08-10 14:40:25 +03:00
|
|
|
if (type.IsObsolete ()) {
|
|
|
|
obsoleted_enums [name] = type;
|
2019-08-14 18:46:55 +03:00
|
|
|
return;
|
2021-08-10 14:40:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (AttributeHelpers.HasAnyObsoleted (type)) {
|
|
|
|
obsoleted_enums [name] = type;
|
|
|
|
return;
|
|
|
|
}
|
2019-08-14 18:46:55 +03:00
|
|
|
|
2016-05-26 16:06:52 +03:00
|
|
|
// e.g. WatchKit.WKErrorCode and WebKit.WKErrorCode :-(
|
2017-12-15 22:08:09 +03:00
|
|
|
if (!enums.TryGetValue (name, out var td))
|
2016-05-26 16:06:52 +03:00
|
|
|
enums.Add (name, type);
|
2017-12-28 16:51:34 +03:00
|
|
|
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;
|
|
|
|
|
2017-12-15 22:08:09 +03:00
|
|
|
var framework = Helpers.GetFramework (decl);
|
|
|
|
if (framework == null)
|
|
|
|
return;
|
|
|
|
|
2016-05-26 16:06:52 +03:00
|
|
|
var mname = Helpers.GetManagedName (name);
|
2021-08-10 14:40:25 +03:00
|
|
|
|
|
|
|
// If our enum is obsoleted, then don't process it.
|
|
|
|
if (obsoleted_enums.ContainsKey (mname))
|
|
|
|
return;
|
|
|
|
|
2017-12-15 22:08:09 +03:00
|
|
|
if (!enums.TryGetValue (mname, out var type)) {
|
|
|
|
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":
|
2019-07-18 12:44:34 +03:00
|
|
|
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":
|
2016-06-28 00:18:30 +03:00
|
|
|
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 (native) {
|
|
|
|
if (!IsNative (type))
|
2017-12-15 22:08:09 +03:00
|
|
|
Log.On (framework).Add ($"!missing-enum-native! {name}");
|
2016-05-26 16:06:52 +03:00
|
|
|
} else {
|
|
|
|
if (IsNative (type))
|
2017-12-15 22:08:09 +03:00
|
|
|
Log.On (framework).Add ($"!extra-enum-native! {name}");
|
2016-05-26 16:06:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int managed_size = 4;
|
2020-10-05 22:57:18 +03:00
|
|
|
bool signed = true;
|
2016-05-26 16:06:52 +03:00
|
|
|
switch (GetEnumUnderlyingType (type).Name) {
|
|
|
|
case "Byte":
|
2020-10-05 22:57:18 +03:00
|
|
|
signed = false;
|
|
|
|
managed_size = 1;
|
|
|
|
break;
|
2016-05-26 16:06:52 +03:00
|
|
|
case "SByte":
|
|
|
|
managed_size = 1;
|
|
|
|
break;
|
|
|
|
case "Int16":
|
2020-10-05 22:57:18 +03:00
|
|
|
managed_size = 2;
|
|
|
|
break;
|
2016-05-26 16:06:52 +03:00
|
|
|
case "UInt16":
|
2020-10-05 22:57:18 +03:00
|
|
|
signed = false;
|
2016-05-26 16:06:52 +03:00
|
|
|
managed_size = 2;
|
|
|
|
break;
|
|
|
|
case "Int32":
|
2020-10-05 22:57:18 +03:00
|
|
|
break;
|
2016-05-26 16:06:52 +03:00
|
|
|
case "UInt32":
|
2020-10-05 22:57:18 +03:00
|
|
|
signed = false;
|
2016-05-26 16:06:52 +03:00
|
|
|
break;
|
|
|
|
case "Int64":
|
2020-10-05 22:57:18 +03:00
|
|
|
managed_size = 8;
|
|
|
|
break;
|
2016-05-26 16:06:52 +03:00
|
|
|
case "UInt64":
|
2020-10-05 22:57:18 +03:00
|
|
|
signed = false;
|
2016-05-26 16:06:52 +03:00
|
|
|
managed_size = 8;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
2020-10-05 22:57:18 +03:00
|
|
|
|
|
|
|
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] = value.ToString ();
|
|
|
|
// 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))
|
|
|
|
Log.On (framework).Add ($"!missing-enum-value! {type.Name} native value {native_signed_values [value]} = {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] = value.ToString ();
|
|
|
|
// 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.On (framework).Add ($"!missing-enum-value! {type.Name} native value {native_unsigned_values [value]} = {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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-26 16:06:52 +03:00
|
|
|
if (native_size != managed_size)
|
2017-12-15 22:08:09 +03:00
|
|
|
Log.On (framework).Add ($"!wrong-enum-size! {name} managed {managed_size} vs native {native_size}");
|
2016-05-26 16:06:52 +03:00
|
|
|
}
|
|
|
|
|
2020-10-05 22:57:18 +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) {
|
2017-12-15 22:08:09 +03:00
|
|
|
var t = extra.Value;
|
2021-08-10 14:40:25 +03:00
|
|
|
if (obsoleted_enums.ContainsKey (t.Name))
|
|
|
|
continue;
|
2017-12-15 22:08:09 +03:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-11 11:06:46 +03:00
|
|
|
}
|