public override void VisitFunctionDecl(FunctionDecl decl, VisitKind visitKind) { if (visitKind == VisitKind.Enter && AttributeHelpers.FindObjcDeprecated(decl.Attrs, out VersionTuple version)) { PlainCDeprecatedFunctions [decl.QualifiedName] = version; } }
public override void VisitObjCMethodDecl(ObjCMethodDecl decl, VisitKind visitKind) { if (visitKind == VisitKind.Enter && AttributeHelpers.FindObjcDeprecated(decl.Attrs, out VersionTuple version)) { ObjCDeprecatedSelectors[decl.QualifiedName] = version; } }
void VisitItem(NamedDecl decl, VisitKind visitKind) { if (visitKind == VisitKind.Enter && AttributeHelpers.FindObjcDeprecated(decl.Attrs, out VersionTuple version)) { ObjCDeprecatedItems[decl.Name] = version; } }
public override void VisitObjCMethodDecl(ObjCMethodDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } // protocol members are checked in ObjCProtocolCheck if (decl.DeclContext is ObjCProtocolDecl) { return; } // don't process methods (or types) that are unavailable for the current platform if (!decl.IsAvailable() || !(decl.DeclContext as Decl).IsAvailable()) { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } string selector = decl.GetSelector(); if (String.IsNullOrEmpty(selector)) { return; } var name = (decl.IsClassMethod ? "+" : String.Empty) + decl.QualifiedName; bool found = qualified_selectors.Contains(name); if (!found) { // a category could be inlined into the type it extend var category = decl.DeclContext as ObjCCategoryDecl; if (category != null) { var cname = category.Name; if (cname == null) { name = GetCategoryBase(category) + name; } else { name = name.ReplaceFirstInstance(cname, GetCategoryBase(category)); } found = qualified_selectors.Contains(name); } } if (!found) { Log.On(framework).Add($"!missing-selector! {name} not bound"); } }
public override void VisitObjCMethodDecl(ObjCMethodDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } Visit(decl); }
public override void VisitObjCInterfaceDecl(ObjCInterfaceDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } if (!decl.IsDefinition) { return; } var name = decl.Name; if (name.EndsWith("Internal", StringComparison.Ordinal)) { return; } // check availability macros to see if the API is available on the OS and not deprecated if (!decl.IsAvailable()) { return; } TypeDefinition td; if (!type_map.TryGetValue(name, out td)) { Console.WriteLine("!missing-type! {0} not bound", name); // other checks can't be done without an actual type to inspect return; } // check base type var nbt = decl.SuperClass?.Name; var mbt = td.BaseType?.Resolve().GetName(); if (nbt != mbt) { Console.WriteLine("!wrong-base-type! {0} expected {1} actual {2}", name, nbt, mbt); } // check protocols foreach (var protocol in decl.Protocols) { var pname = protocol.Name; if (!ImplementProtocol(pname, td)) { Console.WriteLine("!missing-protocol-conformance! {0} should conform to {1}", name, pname); } } // TODO : check for extraneous protocols type_map.Remove(name); }
public override void VisitObjCInterfaceDecl(ObjCInterfaceDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } if (!decl.IsDefinition) { return; } var name = decl.Name; // 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; } if (!type_map.TryGetValue(name, out var td)) { Log.On(framework).Add($"!missing-type! {name} not bound"); // other checks can't be done without an actual type to inspect return; } // check base type var nbt = decl.SuperClass?.Name; var mbt = td.BaseType?.Resolve().GetName(); if (nbt != mbt) { Log.On(framework).Add($"!wrong-base-type! {name} expected {nbt} actual {mbt}"); } // check protocols foreach (var protocol in decl.Protocols) { var pname = protocol.Name; if (!ImplementProtocol(pname, td)) { Log.On(framework).Add($"!missing-protocol-conformance! {name} should conform to {pname}"); } } // TODO : check for extraneous protocols type_map.Remove(name); }
public override void VisitObjCCategoryDecl(ObjCCategoryDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } foreach (var d in decl.Methods) { Visit(d); } }
public override void VisitObjCMethodDecl(ObjCMethodDecl decl, VisitKind visitKind) { if (visitKind == VisitKind.Enter && AttributeHelpers.FindObjcDeprecated(decl.Attrs, out VersionTuple version)) { var qn = decl.QualifiedName; if (decl.IsClassMethod) { qn = "+" + qn; } ObjCDeprecatedSelectors [qn] = version; } }
void VisitItem(NamedDecl decl, VisitKind visitKind) { if (visitKind == VisitKind.Enter && AttributeHelpers.FindObjcDeprecated(decl.Attrs, out VersionTuple version)) { // `(anonymous)` has a null name var name = decl.Name; if (name is not null) { ObjCDeprecatedItems[name] = version; } } }
public override void VisitObjCMethodDecl(ObjCMethodDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } // don't process methods (or types) that are unavailable for the current platform if (!decl.IsAvailable() || !(decl.DeclContext as Decl).IsAvailable()) { return; } var method = GetMethod(decl); // don't report missing [DesignatedInitializer] for types that are not bound - that's a different problem if (method == null) { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } var designated_initializer = method.IsDesignatedInitializer(); if (!method.IsConstructor) { if (designated_initializer) { Log.On(framework).Add($"!incorrect-designated-initializer! {method.GetName ()} is not a constructor"); } } else if (decl.IsDesignatedInitializer) { if (!designated_initializer) { Log.On(framework).Add($"!missing-designated-initializer! {method.GetName ()} is missing an [DesignatedInitializer] attribute"); } } else { if (designated_initializer) { Log.On(framework).Add($"!extra-designated-initializer! {method.GetName ()} is incorrectly decorated with an [DesignatedInitializer] attribute"); } } }
public override void VisitFunctionDecl(FunctionDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } // skip macros : we generally implement them but their name is lost (so no matching is possible) if (!decl.IsExternC) { return; } var name = decl.Name; // do not consider _* or __* as public API that should be bound if (name [0] == '_') { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } // check availability macros to see if the API is available on the OS and not deprecated if (!decl.IsAvailable()) { return; } if (!dllimports.ContainsKey(name)) { // if we find functions without matching DllImport then we report them // but don't report deprecated functions if (!decl.IsDeprecated()) { Log.On(framework).Add($"!missing-pinvoke! {name} is not bound"); } return; } dllimports.Remove(name); }
public override void VisitObjCCategoryDecl(ObjCCategoryDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } var categoryName = decl.Name; if (categoryName == 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; } var ciName = decl.ClassInterface.Name; if (!type_map_copy.TryGetValue(ciName, out var td)) { // other checks can't be done without an actual type to inspect return; } // check protocols foreach (var protocol in decl.Protocols) { var pname = protocol.Name; if (!ImplementProtocol(pname, td)) { Log.On(framework).Add($"!missing-protocol-conformance! {ciName} should conform to {pname} (defined in '{categoryName}' category)"); } } }
public override void VisitCXXRecordDecl(CXXRecordDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter || !decl.IsCompleteDefinition || decl.Name == null) return; nameToDecl[decl.QualifiedName] = decl; switch (decl.QualifiedName) { case "Urho3D::RefCounted": UrhoRefCounted = decl; break; case "Urho3D::Object": UrhoObjectType = decl; break; case "Urho3D::EventHandler": EventHandlerType = decl; break; } }
public override void VisitObjCInterfaceDecl(ObjCInterfaceDecl decl, VisitKind visitKind) { if (visitKind == VisitKind.Enter && !IsPrivate(decl.Name)) { currentInterfaceDecl = decl; currentType = APITestTool.XamarinMacAssembly.GetTypes().FirstOrDefault(x => x.Name == Extrospection.Helpers.GetManagedName(decl.Name)); // Technically SingleOrDefault, but significantly slower // If we couldn't match up a type, we're going to skip testing for now. if (currentType == null) { currentInterfaceDecl = null; } } else { currentInterfaceDecl = null; currentType = null; } }
public override void VisitObjCMethodDecl(ObjCMethodDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } // don't process methods (or types) that are unavailable for the current platform if (!decl.IsAvailable() || !(decl.DeclContext as Decl).IsAvailable()) { return; } // does not look exposed, but part of the dump if (decl.DumpToString().IndexOf("UI_APPEARANCE_SELECTOR", StringComparison.OrdinalIgnoreCase) < 0) { return; } VisitObjCMethodDecl(decl); }
public override void VisitCXXRecordDecl(CXXRecordDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter || !decl.IsCompleteDefinition || decl.Name == null) { return; } nameToDecl[decl.QualifiedName] = decl; switch (decl.QualifiedName) { case "Urho3D::RefCounted": UrhoRefCounted = decl; break; case "Urho3D::Object": UrhoObjectType = decl; break; case "Urho3D::EventHandler": EventHandlerType = decl; break; } }
protected override bool DumpNode(SyntaxNode node, VisitKind visitKind) { string GetIndent(int depth, bool lastChild) => new string(' ', depth * 2); if (visitKind == VisitKind.Enter) { writer.Write(GetIndent(NodeStack.Count - 1, false)); writer.Write(node.GetType().Name); writer.WriteLine(); } else if (visitKind == VisitKind.Leave) { var tokens = NodeStack[0].Tokens; for (int i = 0; i < tokens.Count; i++) { var token = tokens[i]; writer.Write(GetIndent(NodeStack.Count, i == tokens.Count - 1)); writer.Write(token.Kind); switch (token.Kind) { case SyntaxKind.WhiteSpaceTriviaToken: writer.WriteLine(); break; default: writer.Write(" → "); writer.WriteLine(token.SourceText); break; } } } return(true); }
public override void VisitCXXRecordDecl(CXXRecordDecl decl, VisitKind visitKind) { //Console.WriteLine($"{decl.QualifiedName} {visitKind} "); if (decl.QualifiedName == ("Urho3D::String")) { //Console.WriteLine($"{decl.QualifiedName} {visitKind} {decl.IsCompleteDefinition}"); } if (visitKind != VisitKind.Enter || !decl.IsCompleteDefinition || decl.Name == null) { return; } if (visitKind == VisitKind.Leave) currentType = null; BaseNodeType baseNodeType; if (baseNodeTypes.TryGetValue(decl.QualifiedName, out baseNodeType)) { baseNodeType.Decl = decl; baseNodeType.Bind(); return; } foreach (var bnt in baseNodeTypes.Values) { if (bnt.Decl != null && decl.IsDerivedFrom(bnt.Decl)) { bnt.Bind(decl); return; } } }
public override void VisitEnumDecl(EnumDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter || !decl.IsCompleteDefinition || decl.QualifiedName == null) return; if (!decl.QualifiedName.StartsWith ("Urho")) { if (!decl.QualifiedName.ToLower ().Contains ("loopmode2d")) return; } //Console.WriteLine($"VisitingType: {decl.QualifiedName}"); string typeName = RemapTypeName(decl.Name); PushType(new TypeDeclaration { Name = typeName, ClassType = ClassType.Enum, Modifiers = Modifiers.Public }, StringUtil.GetTypeComments(decl)); foreach (var constantDecl in decl.Decls<EnumConstantDecl>()) { var valueName = RemapEnumName (typeName, constantDecl.Name); switch (valueName) { // LIST HERE ANY Values we want to skip case "foo": case null: continue; } var x = new EnumMemberDeclaration(); var enumValue = new EnumMemberDeclaration { Name = valueName }; if (constantDecl.InitExpr != null) { APSInt v; constantDecl.InitExpr.EvaluateAsInt (decl.AstContext, out v); var ul = v.GetLimitedValue (); PrimitiveExpression value; if ((ul & 0xffffffff) == ul) value = new PrimitiveExpression ((int)ul); else throw new NotImplementedException ($"Got a {ul} value which will not fit on an int, you must manually handle this case in the generatorg"); enumValue.Initializer = value; } currentType.Members.Add(enumValue); } }
public override void VisitObjCCategoryDecl(ObjCCategoryDecl decl, VisitKind visitKind) => VisitItem(decl, visitKind);
public virtual bool VisitAttributeStatementSyntax( AttributeStatementSyntax attributeStatement, VisitKind visitKind) => VisitStatementSyntax(attributeStatement, visitKind);
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; } var mname = Helpers.GetManagedName(name); if (!enums.TryGetValue(mname, out var type)) { Log.On(framework).Add($"!missing-enum! {name} not bound"); 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": 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": case "OSStatus": break; case "int64_t": case "uint64_t": case "unsigned long long": case "CVOptionFlags": // uint64_t 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)) { Log.On(framework).Add($"!missing-enum-native! {name}"); } } else { if (IsNative(type)) { Log.On(framework).Add($"!extra-enum-native! {name}"); } } int managed_size = 4; switch (GetEnumUnderlyingType(type).Name) { case "Byte": case "SByte": managed_size = 1; break; case "Int16": case "UInt16": managed_size = 2; break; case "Int32": case "UInt32": break; case "Int64": case "UInt64": managed_size = 8; break; default: throw new NotImplementedException(); } if (native_size != managed_size) { Log.On(framework).Add($"!wrong-enum-size! {name} managed {managed_size} vs native {native_size}"); } }
public override void VisitObjCProtocolDecl(ObjCProtocolDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } if (!decl.IsDefinition) { 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; } var name = decl.Name; TypeDefinition td; if (!protocol_map.TryGetValue(name, out td)) { if (!decl.IsDeprecated()) { Log.On(framework).Add($"!missing-protocol! {name} not bound"); } // other checks can't be done without an actual protocol to inspect return; } // build type selector-required map var map = new Dictionary <string, bool> (); foreach (var ca in td.CustomAttributes) { string export = null; string g_export = null; string s_export = null; bool is_required = false; bool is_property = false; bool is_static = false; switch (ca.Constructor.DeclaringType.Name) { case "ProtocolMemberAttribute": foreach (var p in ca.Properties) { switch (p.Name) { case "Selector": export = p.Argument.Value as string; break; case "GetterSelector": g_export = p.Argument.Value as string; break; case "SetterSelector": s_export = p.Argument.Value as string; break; case "IsRequired": is_required = (bool)p.Argument.Value; break; case "IsProperty": is_property = (bool)p.Argument.Value; break; case "IsStatic": is_static = (bool)p.Argument.Value; break; } } break; } if (is_property) { if (g_export != null) { if (is_static) { g_export = "+" + g_export; } map.Add(g_export, is_required); } if (s_export != null) { if (is_static) { s_export = "+" + s_export; } map.Add(s_export, is_required); } } else if (export != null) { if (is_static) { export = "+" + export; } map.Add(export, is_required); } } var deprecatedProtocol = (decl.DeclContext as Decl).IsDeprecated(); // don't report anything for deprecated protocols // (we still report some errors for deprecated members of non-deprecated protocols - because abstract/non-abstract can // change the managed API and we want to get things right, even if for deprecated members). if (!deprecatedProtocol) { var remaining = new Dictionary <string, bool> (map); // check that required members match the [Abstract] members foreach (ObjCMethodDecl method in decl.Methods) { // some members might not be part of the current platform if (!method.IsAvailable()) { continue; } var selector = GetSelector(method); if (selector == null) { continue; } // a .NET interface cannot have constructors - so we cannot enforce that on the interface if (IsInit(selector)) { continue; } if (method.IsClassMethod) { selector = "+" + selector; } bool is_abstract; if (map.TryGetValue(selector, out is_abstract)) { bool required = method.ImplementationControl == ObjCImplementationControl.Required; if (required) { if (!is_abstract) { Log.On(framework).Add($"!incorrect-protocol-member! {GetName (decl, method)} is REQUIRED and should be abstract"); } } else { if (is_abstract) { Log.On(framework).Add($"!incorrect-protocol-member! {GetName (decl, method)} is OPTIONAL and should NOT be abstract"); } } remaining.Remove(selector); } else if (!method.IsClassMethod) { // a .NET interface cannot have static methods - so we can only report missing instance methods if (!decl.IsDeprecated()) { Log.On(framework).Add($"!missing-protocol-member! {GetName (decl, method)} not found"); } remaining.Remove(selector); } } foreach (var selector in remaining.Keys) { Log.On(framework).Add($"!extra-protocol-member! unexpected selector {decl.Name}::{selector} found"); } remaining.Clear(); } map.Clear(); protocol_map.Remove(name); }
public virtual bool VisitEdgeStatementSyntax( EdgeStatementSyntax edgeStatement, VisitKind visitKind) => VisitStatementSyntax(edgeStatement, visitKind);
public override void VisitObjCMethodDecl(ObjCMethodDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } // don't process methods (or types) that are unavailable for the current platform if (!decl.IsAvailable() || !(decl.DeclContext as Decl).IsAvailable()) { return; } var method = GetMethod(decl); // don't report missing nullability on types that are not bound - that's a different problem if (method == null) { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } var t = method.DeclaringType; // look for [NullableContext] for defaults var managed_default_nullability = GetNullableContext(method); if (managed_default_nullability == Null.Oblivious) { managed_default_nullability = GetNullableContext(t); } // check parameters // categories have an offset of 1 for the extension method type (spotted as static types) int i = t.IsSealed && t.IsAbstract ? 1 : 0; foreach (var p in decl.Parameters) { var mp = method.Parameters [i++]; // a managed `out` value does not need to be inialized, won't be null (but can be ignored) if (mp.IsOut) { continue; } var pt = mp.ParameterType; // if bound as `IntPtr` then nullability attributes won't be present if (pt.IsValueType) { continue; } Null parameter_nullable; // if we used a type by reference (e.g. `ref float foo`); or a nullable type (e.g. `[BindAs]`) // then assume it's meant as a nullable type) without additional decorations if (pt.IsByReference || pt.FullName.StartsWith("System.Nullable`1<", StringComparison.Ordinal)) { parameter_nullable = Null.Annotated; } else { // check C# 8 compiler attributes var nullable = GetNullable(mp); if (nullable.Length > 1) { // check the type itself, TODO check the generics (don't think we have such cases yet) parameter_nullable = nullable [0]; } else if (nullable.Length == 0) { parameter_nullable = managed_default_nullability; } else { parameter_nullable = nullable [0]; } } // match with native and, if needed, report discrepancies p.QualType.Type.GetNullability(p.AstContext, out var nullability); switch (nullability) { case NullabilityKind.NonNull: if (parameter_nullable == Null.Annotated) { Log.On(framework).Add($"!extra-null-allowed! '{method.FullName}' has a extraneous [NullAllowed] on parameter #{i-1}"); } break; case NullabilityKind.Nullable: if (parameter_nullable != Null.Annotated) { Log.On(framework).Add($"!missing-null-allowed! '{method.FullName}' is missing an [NullAllowed] on parameter #{i-1}"); } break; case NullabilityKind.Unspecified: break; } } // with .net a constructor will always return something (or throw) // that's not the case in ObjC where `init*` can return `nil` if (method.IsConstructor) { return; } var mrt = method.ReturnType; // if bound as an `IntPtr` then the nullability will not be visible in the metadata if (mrt.IsValueType) { return; } Null return_nullable; // if we used a nullable type (e.g. [BindAs] then assume it's meant as a nullable type) without additional decorations if (mrt.FullName.StartsWith("System.Nullable`1<", StringComparison.Ordinal)) { return_nullable = Null.Annotated; } else { ICustomAttributeProvider cap; // the managed attributes are on the property, not the special methods if (method.IsGetter) { var property = method.FindProperty(); // also `null_resettable` will only show something (natively) on the setter (since it does not return null, but accept it) // in this case we'll trust xtro checking the setter only (if it exists, if not then it can't be `null_resettable`) if (property.SetMethod != null) { return; } cap = property; } else { cap = method.MethodReturnType; } Null [] mrt_nullable = GetNullable(cap); if (mrt_nullable.Length > 1) { // check the type itself, TODO check the generics (don't think we have such cases yet) return_nullable = mrt_nullable [0]; } else if (mrt_nullable.Length == 0) { return_nullable = managed_default_nullability; } else { return_nullable = mrt_nullable [0]; } } var rt = decl.ReturnQualType; rt.Type.GetNullability(decl.AstContext, out var rnull); switch (rnull) { case NullabilityKind.NonNull: if (return_nullable == Null.Annotated) { Log.On(framework).Add($"!extra-null-allowed! '{method}' has a extraneous [NullAllowed] on return type"); } break; case NullabilityKind.Nullable: if (return_nullable != Null.Annotated) { Log.On(framework).Add($"!missing-null-allowed! '{method}' is missing an [NullAllowed] on return type"); } break; case NullabilityKind.Unspecified: break; } }
public override void VisitCXXMethodDecl(CXXMethodDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) return; var isConstructor = decl is CXXConstructorDecl; if (decl is CXXDestructorDecl || isConstructor) return; if (decl.IsCopyAssignmentOperator || decl.IsMoveAssignmentOperator) return; if (decl.Parent == null) return; if (!decl.Parent.QualifiedName.StartsWith("Urho3D::")) return; // Only get methods prefixed with Get with no parameters // and Set methods that return void and take a single parameter var name = decl.Name; // Handle Get methods that are not really getters // This is a get method that does not get anything QualType type; if (name.StartsWith("Get") || name.StartsWith("Is")) { if (decl.Parameters.Count() != 0) return; if (decl.ReturnQualType.ToString() == "void") return; if (name == "IsElementEventSender" || name == "IsOpen" || name == "IsPressed") return; type = decl.ReturnQualType; } else if (name.StartsWith("Set")) { if (decl.Parameters.Count() != 1) return; if (!(decl.ReturnQualType.Bind() is Sharpie.Bind.Types.VoidType)) return; if ((name == "SetTypeName" || name == "SetType") && decl.Parent.Name == "UnknownComponent") return; if (decl.Access != AccessSpecifier.Public) return; type = decl.Parameters.FirstOrDefault().QualType; } else return; Dictionary<string, Dictionary<QualType, GetterSetter>> typeProperties; if (!allProperties.TryGetValue(decl.Parent.Name, out typeProperties)) { typeProperties = new Dictionary<string, Dictionary<QualType, GetterSetter>>(); allProperties[decl.Parent.Name] = typeProperties; } var propName = name.Substring(name.StartsWith("Is") ? 2 : 3); Dictionary<QualType, GetterSetter> property; if (!typeProperties.TryGetValue(propName, out property)) { property = new Dictionary<QualType, GetterSetter>(); typeProperties[propName] = property; } GetterSetter gs; if (!property.TryGetValue(type, out gs)) { gs = new GetterSetter() { Name = propName }; } if (name.StartsWith("Get") || name.StartsWith("Is")) { Console.WriteLine($"Getter exists for {name}"); gs.Getter = decl; } else { if (gs.Setter != null) { Console.WriteLine($"Setter exists for {name}"); } gs.Setter = decl; } property[type] = gs; }
public override void VisitCXXMethodDecl(CXXMethodDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } var isConstructor = decl is CXXConstructorDecl; if (decl is CXXDestructorDecl || isConstructor) { return; } if (decl.IsCopyAssignmentOperator || decl.IsMoveAssignmentOperator) { return; } if (decl.Parent == null) { return; } if (!decl.Parent.QualifiedName.StartsWith("Urho3D::")) { return; } // Only get methods prefixed with Get with no parameters // and Set methods that return void and take a single parameter var name = decl.Name; // Handle Get methods that are not really getters // This is a get method that does not get anything QualType type; if (name.StartsWith("Get") || name.StartsWith("Is")) { if (decl.Parameters.Count() != 0) { return; } if (decl.ReturnQualType.ToString() == "void") { return; } if (name == "IsElementEventSender" || name == "IsOpen" || name == "IsPressed") { return; } type = decl.ReturnQualType; } else if (name.StartsWith("Set")) { if (decl.Parameters.Count() != 1) { return; } if (!(decl.ReturnQualType.Bind() is Sharpie.Bind.Types.VoidType)) { return; } if ((name == "SetTypeName" || name == "SetType") && decl.Parent.Name == "UnknownComponent") { return; } if (decl.Access != AccessSpecifier.Public) { return; } type = decl.Parameters.FirstOrDefault().QualType; } else { return; } Dictionary <string, Dictionary <QualType, GetterSetter> > typeProperties; if (!allProperties.TryGetValue(decl.Parent.Name, out typeProperties)) { typeProperties = new Dictionary <string, Dictionary <QualType, GetterSetter> >(); allProperties[decl.Parent.Name] = typeProperties; } var propName = name.Substring(name.StartsWith("Is") ? 2 : 3); Dictionary <QualType, GetterSetter> property; if (!typeProperties.TryGetValue(propName, out property)) { property = new Dictionary <QualType, GetterSetter>(); typeProperties[propName] = property; } GetterSetter gs; if (!property.TryGetValue(type, out gs)) { gs = new GetterSetter() { Name = propName }; } if (name.StartsWith("Get") || name.StartsWith("Is")) { Console.WriteLine($"Getter exists for {name}"); if (propName != decl.Parent.Name || propName == "Text") { // do not generate Getter if propertyName equals to typename (Text type already has a workaround for this case) gs.Getter = decl; } } else { if (gs.Setter != null) { Console.WriteLine($"Setter exists for {name}"); } gs.Setter = decl; } property[type] = gs; }
public override void VisitObjCMethodDecl(ObjCMethodDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } // don't process methods (or types) that are unavailable for the current platform if (!decl.IsAvailable() || !(decl.DeclContext as Decl).IsAvailable()) { return; } var framework = Helpers.GetFramework(decl); if (framework == null) { return; } var simd_type = string.Empty; var requires_marshal_directive = false; var native_simd = ContainsSimdTypes(decl, ref simd_type, ref requires_marshal_directive); ManagedSimdInfo info; managed_methods.TryGetValue(decl.GetName(), out info); var method = info?.Method; if (!native_simd) { if (method != null) { // The managed method uses types that were incorrectly used in place of the correct Simd types, // but the native method doesn't use the native Simd types. This means the binding is correct. } else { // Neither the managed nor the native method have anything to do with Simd types. } return; } if (method == null) { // Could not map the native method to a managed method. // This needs investigation, to see why the native method couldn't be mapped. // Check if this is new API, in which case it probably couldn't be mapped because we haven't bound it. var is_new = false; var attrs = decl.Attrs.ToList(); var parentClass = decl.DeclContext as Decl; if (parentClass != null) { attrs.AddRange(parentClass.Attrs); } foreach (var attr in attrs) { var av_attr = attr as AvailabilityAttr; if (av_attr == null) { continue; } if (av_attr.Platform.Name != "ios") { continue; } if (av_attr.Introduced.Major >= 11) { is_new = true; break; } } if (is_new && !very_strict) { return; } if (!strict) { return; } Log.On(framework).Add($"!missing-simd-managed-method! {decl}: could not find a managed method for the native method {decl.GetName ()} (selector: {decl.Selector}). Found the simd type '{simd_type}' in the native signature."); return; } if (!info.ContainsInvalidMappingForSimd) { // The managed method does not have any types that are incorrect for Simd. if (requires_marshal_directive) { CheckMarshalDirective(method, simd_type); } return; } if (method.IsObsolete()) { // We have a potentially broken managed method, but it's obsolete. That's fine. return; } if (requires_marshal_directive) { CheckMarshalDirective(method, simd_type); } // We have a potentially broken managed method. This needs fixing/investigation. Log.On(framework).Add($"!unknown-simd-type-in-signature! {method}: the native signature has a simd type ({simd_type}), while the corresponding managed method is using an incorrect (non-simd) type."); }
public override void VisitFunctionDecl(FunctionDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } // skip macros : we generally implement them but their name is lost (so no matching is possible) if (!decl.IsExternC) { return; } var name = decl.Name; // do not consider _* or __* as public API that should be bound if (name [0] == '_') { return; } // exclude non-framework code (e.g. all unix headers) var header_file = decl.PresumedLoc.FileName; var fxh = header_file.IndexOf(".framework/Headers/", StringComparison.Ordinal); var framework = String.Empty; if (fxh > 0) { var start = header_file.LastIndexOf('/', fxh) + 1; framework = header_file.Substring(start, fxh - start); } else { // FIXME: we only process the headers in Frameworks, not all the UNIX headers // that still miss a few things (like objc runtime) but nothing that *requires* binding return; } // check availability macros to see if the API is available on the OS and not deprecated if (!decl.IsAvailable()) { return; } MethodDefinition md; if (!dllimports.TryGetValue(name, out md)) { // FIXME: we ignore some frameworks we have not bound (too many entries for our data files) // we do this late because, in some cases like vImage, we do bind a few API switch (framework) { // Accelerate and friends case "vImage": case "vecLib": return; // security case "GSS": return; // exposed in OpenTK-1.dll case "OpenAL": case "OpenGLES": return; } // if we find functions without matching DllImport then we report them Console.WriteLine("!missing-pinvoke! {0} is not bound", name); return; } dllimports.Remove(name); }
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; } 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)) { Log.On(framework).Add($"!missing-enum! {name} not bound"); 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": 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": case "OSStatus": break; case "int64_t": case "uint64_t": case "unsigned long long": case "CVOptionFlags": // uint64_t 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)) { Log.On(framework).Add($"!missing-enum-native! {name}"); } } else { if (IsNative(type)) { Log.On(framework).Add($"!extra-enum-native! {name}"); } } int managed_size = 4; bool signed = true; switch (GetEnumUnderlyingType(type).Name) { case "Byte": signed = false; managed_size = 1; break; case "SByte": managed_size = 1; break; case "Int16": managed_size = 2; break; case "UInt16": signed = false; managed_size = 2; break; case "Int32": break; case "UInt32": signed = false; break; case "Int64": managed_size = 8; break; case "UInt64": signed = false; 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] = 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"); } } } if (native_size != managed_size) { Log.On(framework).Add($"!wrong-enum-size! {name} managed {managed_size} vs native {native_size}"); } }
public override void VisitObjCProtocolDecl(ObjCProtocolDecl decl, VisitKind visitKind) { if (visitKind != VisitKind.Enter) { return; } if (!decl.IsDefinition) { return; } // check availability macros to see if the API is available on the OS and not deprecated if (!decl.IsAvailable()) { return; } var name = decl.Name; TypeDefinition td; if (!protocol_map.TryGetValue(name, out td)) { Console.WriteLine("!missing-protocol! {0} not bound", name); // other checks can't be done without an actual protocol to inspect return; } // build type selector-required map var map = new Dictionary <string, bool> (); foreach (var ca in td.CustomAttributes) { string export = null; string g_export = null; string s_export = null; bool is_required = false; bool is_property = false; switch (ca.Constructor.DeclaringType.Name) { case "ProtocolMemberAttribute": foreach (var p in ca.Properties) { switch (p.Name) { case "Selector": export = p.Argument.Value as string; break; case "GetterSelector": g_export = p.Argument.Value as string; break; case "SetterSelector": s_export = p.Argument.Value as string; break; case "IsRequired": is_required = (bool)p.Argument.Value; break; case "IsProperty": is_property = (bool)p.Argument.Value; break; } } break; } if (is_property) { if (g_export != null) { map.Add(g_export, is_required); } if (s_export != null) { map.Add(s_export, is_required); } } else { if (export != null) { map.Add(export, is_required); } } } var remaining = new Dictionary <string, bool> (map); // check that required members match the [Abstract] members foreach (ObjCMethodDecl method in decl.Methods) { // some members might not be part of the current platform if (!method.IsAvailable()) { continue; } var selector = GetSelector(method); if (selector == null) { continue; } // a .NET interface cannot have constructors - so we cannot enforce that on the interface if (IsInit(selector)) { continue; } bool is_abstract; if (map.TryGetValue(selector, out is_abstract)) { bool required = method.ImplementationControl == ObjCImplementationControl.Required; if (required) { if (!is_abstract) { Console.WriteLine("!incorrect-protocol-member! {0} is REQUIRED and should be abstract", GetName(decl, method)); } } else { if (is_abstract) { Console.WriteLine("!incorrect-protocol-member! {0} is OPTIONAL and should NOT be abstract", GetName(decl, method)); } } remaining.Remove(selector); } else if (!method.IsClassMethod) { // a .NET interface cannot have static methods - so we can only report missing instance methods Console.WriteLine("!missing-protocol-member! {0} not found", GetName(decl, method)); remaining.Remove(selector); } } foreach (var selector in remaining.Keys) { Console.WriteLine("!extra-protocol-member! unexpected selector {0}::{1} found", decl.Name, selector); } remaining.Clear(); map.Clear(); protocol_map.Remove(name); }
public virtual bool VisitNodeStatementSyntax( NodeStatementSyntax nodeStatement, VisitKind visitKind) => VisitStatementSyntax(nodeStatement, visitKind);
// // Determines if we should bind a method that the C++ API scanner found // bool MethodIsBindable (CXXMethodDecl decl, VisitKind visitKind) { // We only care about enter visits, not leave if (visitKind != VisitKind.Enter) return false; // Global definitions, not inside a class, skip if (currentType == null) return false; // Do not bother with constructors in abstract classes var isConstructor = decl is CXXConstructorDecl; if (isConstructor && decl.Parent.IsAbstract) return false; // Do not wrap constructors if (decl is CXXDestructorDecl) return false; // Not supported in C# if (decl.IsCopyAssignmentOperator || decl.IsMoveAssignmentOperator) return false; // TODO: temporary, do not handle opreators if (!isConstructor && decl.Name.StartsWith ("operator", StringComparison.Ordinal)) return false; // Sanity check if (RemapTypeName (decl.Parent.Name) != currentType.Name) { //Console.WriteLine("For some reason we go t amethod that does not belong here {0}.{1} on {2}", decl.Parent.Name, decl.Name, currentType.Name); return false; } // We only bind the public API if (decl.Access != AccessSpecifier.Public) return false; // Skip blacklisted methods if (SkipMethod (decl)) return false; // Temporary: while we add support for other things, just to debug things // remove types we do not support int variantArgumentsCount = 0; foreach (var p in decl.Parameters) { bool isVariantArgument = IsVariantType(p.QualType); if (isVariantArgument) variantArgumentsCount++; if (IsUnsupportedType(p.QualType, returnType: false) && !isVariantArgument) { //Console.WriteLine($"Bailing out on {p.QualType} from {decl.QualifiedName}"); return false; } } if (variantArgumentsCount > 1) return false; //it won't be easy to handle if it has more than one Variant argument if (IsUnsupportedType(decl.ReturnQualType, returnType: true)) {//variant return type is not support yet //Console.WriteLine($"RETURN Bailing out on {decl.ReturnQualType} from {decl.QualifiedName}"); return false; } return true; }
public override void VisitCXXMethodDecl(CXXMethodDecl decl, VisitKind visitKind) { if (!MethodIsBindable (decl, visitKind)) return; var cmethodBuilder = new StringBuilder(); AstType pinvokeReturn, methodReturn; WrapKind returnIsWrapped; ICSharpCode.NRefactory.CSharp.ParameterModifier pinvokeMod, methodMod; LookupMarshalTypes(decl.ReturnQualType, out pinvokeReturn, out pinvokeMod, out methodReturn, out methodMod, out returnIsWrapped, isReturn: true); var methodReturn2 = methodReturn.Clone(); var propertyInfo = ScanBaseTypes.GetPropertyInfo(decl); if (propertyInfo != null) { propertyInfo.HostType = currentType; if (decl.Name.StartsWith("Get") || decl.Name.StartsWith("Is")) propertyInfo.MethodReturn = methodReturn.Clone(); } var methodName = decl.Name; if (currentTypeNames.Contains(methodName)) methodName += (uniqueMethodName++).ToString(); currentTypeNames.Add(methodName); // // PInvoke declaration + C counterpart declaration // string pinvoke_name = currentType.Name + "_" + methodName; var isConstructor = decl is CXXConstructorDecl; if (isConstructor) { pinvokeReturn = new SimpleType ("IntPtr"); // Do not bind a default constructor for Skeleton if (currentType.Name == "Skeleton") return; } var pinvoke = new MethodDeclaration { Name = pinvoke_name, ReturnType = pinvokeReturn, Modifiers = Modifiers.Extern | Modifiers.Static | Modifiers.Internal }; if (!decl.IsStatic && !isConstructor) pinvoke.Parameters.Add(new ParameterDeclaration(new SimpleType("IntPtr"), "handle")); var dllImport = new Attribute { Type = new SimpleType("DllImport") }; dllImport.Arguments.Add (new PrimitiveExpression ("mono-urho")); dllImport.Arguments.Add (new AssignmentExpression (new IdentifierExpression ("CallingConvention"), csParser.ParseExpression ("CallingConvention.Cdecl"))); pinvoke.Attributes.Add(new AttributeSection(dllImport)); // The C counterpart var cinvoke = new StringBuilder(); string marshalReturn = "{0}"; string creturnType = CleanTypeCplusplus(decl.ReturnQualType); switch (creturnType) { case "bool": creturnType = "int"; break; case "Urho3D::StringHash": creturnType = "int"; marshalReturn = "({0}).Value ()"; break; case "Urho3D::String": case "const Urho3D::String &": case "const class Urho3D::String &": creturnType = "const char *"; marshalReturn = "strdup(({0}).CString ())"; break; case "const struct Urho3D::TileMapInfo2D &": creturnType = "Urho3D::TileMapInfo2D"; break; case "const struct Urho3D::CrowdObstacleAvoidanceParams &": creturnType = "Urho3D::CrowdObstacleAvoidanceParams"; break; case "const class Urho3D::Vector3 &": case "const class Urho3D::Vector2 &": case "const class Urho3D::Vector4 &": case "const class Urho3D::IntVector2 &": case "const class Urho3D::Quaternion &": case "const class Urho3D::Plane &": case "const class Urho3D::BoundingBox &": case "const class Urho3D::Color &": case "Urho3D::Vector3": case "Urho3D::Vector2": case "Urho3D::Vector4": case "Urho3D::IntVector2": case "Urho3D::Quaternion": case "Urho3D::Plane": case "Urho3D::BoundingBox": case "Urho3D::Color": var nsIndex = creturnType.IndexOf ("Urho3D::") + "Urho3D".Length; creturnType = "Interop" + creturnType.Remove (0, nsIndex).Trim ('&', ' ') + " "; marshalReturn = "*((" + creturnType + " *) &({0}))"; break; } if (creturnType.StartsWith("SharedPtr<")) { creturnType = creturnType.ExtractGenericParameter().DropClassOrStructPrefix() + " *"; marshalReturn = "auto copy = {0};\n" + "\tauto plain = copy.Get();\n" + "\tcopy.Detach();\n" + "\tdelete copy;\n" + "\treturn plain;"; } const string methodNameSuffix = "%MethodSuffix%"; const string variantConverterMask = "%VariantConvertor%"; if (isConstructor) cmethodBuilder.Append($"DllExport void *\n{pinvoke_name}{methodNameSuffix} ("); else cmethodBuilder.Append($"DllExport {creturnType}\n{pinvoke_name}{methodNameSuffix} ("); if (decl.IsStatic) { cinvoke.Append($"{decl.Parent.Name}::{decl.Name} ("); } else if (isConstructor) { cinvoke.Append($"new {decl.Name}("); } else { cmethodBuilder.Append($"Urho3D::{decl.Parent.Name} *_target"); if (decl.Parameters.Any()) cmethodBuilder.Append(", "); cinvoke.Append($"_target->{decl.Name} ("); } // // Method declaration // MethodDeclaration method = null; ConstructorDeclaration constructor = null; if (isConstructor) { constructor = new ConstructorDeclaration { Name = RemapMemberName(decl.Parent.Name, decl.Name), Modifiers = (decl.IsStatic ? Modifiers.Static : 0) | (propertyInfo != null ? Modifiers.Private : Modifiers.Public) | (decl.Name == "ToString" ? Modifiers.Override : 0) }; constructor.Body = new BlockStatement(); } else { method = new MethodDeclaration { Name = RemapMemberName(decl.Parent.Name, decl.Name), ReturnType = methodReturn, Modifiers = (decl.IsStatic ? Modifiers.Static : 0) | (propertyInfo != null ? Modifiers.Private : Modifiers.Public) }; method.Body = new BlockStatement(); } // // Marshal from C# to C and the C support to call into C++ // var invoke = new InvocationExpression(new IdentifierExpression(pinvoke_name)); if (!decl.IsStatic && !isConstructor) invoke.Arguments.Add(new IdentifierExpression("handle")); bool first = true; int anonymousParameterNameCount = 1; int currentParamCount = -1; foreach (var param in decl.Parameters) { currentParamCount++; AstType pinvokeParameter, parameter; WrapKind wrapKind; if (!first) { cinvoke.Append(", "); cmethodBuilder.Append(", "); } else first = false; LookupMarshalTypes(param.QualType, out pinvokeParameter, out pinvokeMod, out parameter, out methodMod, out wrapKind); string paramName = param.Name; if (string.IsNullOrEmpty(paramName)) paramName = "param" + (anonymousParameterNameCount++); Expression parameterReference = new IdentifierExpression (paramName); switch (currentType.Name) { case "Input": switch (decl.Name) { case "GetMouseButtonDown": case "GetMouseButtonPress": parameter = new SimpleType ("MouseButton"); parameterReference = new CastExpression (new PrimitiveType ("int"), parameterReference); break; case "GetKeyPress": case "GetKeyDown": case "GetScancodeFromKey": case "GetKeyName": if (currentParamCount == 0 && paramName == "key") { parameter = new SimpleType ("Key"); parameterReference = new CastExpression (new PrimitiveType ("int"), parameterReference); } break; } break; case "VertexBuffer": switch (decl.Name) { case "SetSize": if (currentParamCount == 1 && paramName == "elementMask") { parameter = new SimpleType ("ElementMask"); parameterReference = new CastExpression (new PrimitiveType ("uint"), parameterReference); } break; case "GetVertexSize": if (currentParamCount == 0 && paramName == "elementMask") { parameter = new SimpleType ("ElementMask"); parameterReference = new CastExpression (new PrimitiveType ("uint"), parameterReference); } break; case "GetElementOffset": if (currentParamCount == 0 && paramName == "elementMask") { parameter = new SimpleType ("ElementMask"); parameterReference = new CastExpression (new PrimitiveType ("uint"), parameterReference); } break; } break; case "Log": switch (decl.Name) { case "Write": if (currentParamCount == 0 && paramName == "level") { parameter = new SimpleType ("LogLevel"); parameterReference = new CastExpression (new PrimitiveType ("int"), parameterReference); } break; } break; } if (constructor == null) method.Parameters.Add(new ParameterDeclaration(parameter, paramName, methodMod)); else constructor.Parameters.Add(new ParameterDeclaration(parameter, paramName, methodMod)); pinvoke.Parameters.Add(new ParameterDeclaration(pinvokeParameter, paramName, pinvokeMod)); switch (wrapKind) { case WrapKind.None: invoke.Arguments.Add(parameterReference); break; case WrapKind.HandleMember: case WrapKind.UrhoObject: var cond = new ConditionalExpression (new BinaryOperatorExpression (new CastExpression (new PrimitiveType ("object"), parameterReference), BinaryOperatorType.Equality, new PrimitiveExpression (null)), csParser.ParseExpression ("IntPtr.Zero"), csParser.ParseExpression (paramName + ".Handle")); invoke.Arguments.Add (cond); break; case WrapKind.EventHandler: invoke.Arguments.Add(parameterReference); break; case WrapKind.StringHash: invoke.Arguments.Add (csParser.ParseExpression (paramName + ".Code")); break; case WrapKind.RefBlittable: invoke.Arguments.Add (new DirectionExpression (FieldDirection.Ref, parameterReference)); break; case WrapKind.VectorSharedPtr: throw new NotImplementedException ("Vector marshaling not supported for parameters yet"); } var ctype = CleanTypeCplusplus (param.QualType); string paramInvoke = paramName; switch (ctype) { case "bool": ctype = "int"; break; case "Urho3D::Deserializer &": case "Urho3D::Serializer &": ctype = "File *"; paramInvoke = $"*{paramInvoke}"; break; case "const class Urho3D::String &": ctype = "const char *"; paramInvoke = $"Urho3D::String({paramInvoke})"; break; case "Urho3D::StringHash": ctype = "int"; paramInvoke = $"Urho3D::StringHash({paramInvoke})"; break; case "const class Urho3D::Variant &": paramInvoke = $"{variantConverterMask}({paramInvoke})"; break; } cmethodBuilder.Append($"{ctype} {paramName}"); cinvoke.Append($"{paramInvoke}"); } cinvoke.Append(")"); cmethodBuilder.Append(")\n{\n\t"); // if the type has a ctor accepting Context - add a parameterless one that will use "this(Application.CurrentContext)" if (isConstructor && decl.Parameters.Count() == 1 && decl.Parameters.ElementAt(0).QualType.ToString() == "class Urho3D::Context *") { var ctor = new ConstructorDeclaration { Modifiers = Modifiers.Public, Body = new BlockStatement(), Initializer = new ConstructorInitializer { ConstructorInitializerType = ConstructorInitializerType.This } }; ctor.Initializer.Arguments.Add(csParser.ParseExpression("Application.CurrentContext")); currentType.Members.Add(ctor); } if (method != null && methodReturn is Sharpie.Bind.Types.VoidType) { method.Body.Add(invoke); // pn ($"fprintf (stderr,\"DEBUG {creturnType} {pinvoke_name} (...)\\n\");"); cmethodBuilder.AppendLine($"{cinvoke.ToString()};"); } else { ReturnStatement ret = null; Expression returnExpression; if (!isConstructor) ret = new ReturnStatement(); switch (returnIsWrapped) { case WrapKind.HandleMember: returnExpression = new InvocationExpression (new MemberReferenceExpression (new IdentifierExpression ("Runtime"), "LookupRefCounted", methodReturn2), invoke); break; case WrapKind.UrhoObject: returnExpression = new InvocationExpression (new MemberReferenceExpression (new IdentifierExpression ("Runtime"), "LookupObject", methodReturn2), invoke); break; case WrapKind.EventHandler: returnExpression = invoke; break; case WrapKind.StringHash: returnExpression = new ObjectCreateExpression (new SimpleType ("StringHash"), invoke); break; case WrapKind.MarshalPtrToString: returnExpression = new InvocationExpression(new MemberReferenceExpression(new IdentifierExpression("Marshal"), "PtrToStringAnsi"), invoke); break; case WrapKind.MarshalPtrToStruct: returnExpression = new InvocationExpression(new MemberReferenceExpression(new IdentifierExpression("Marshal"), "PtrToStructure"), invoke, new TypeOfExpression(methodReturn2)); returnExpression = new CastExpression(methodReturn2.Clone(), returnExpression); break; case WrapKind.VectorSharedPtr: var cacheName = "_" + method.Name + "_cache"; var f = new FieldDeclaration () { ReturnType = method.ReturnType.Clone (), Modifiers = Modifiers.Private | (method.Modifiers & Modifiers.Static) }; f.Variables.Add (new VariableInitializer (cacheName, null)); currentType.Members.Add (f); var sharedPtrType = (methodReturn as SimpleType).TypeArguments.First ().Clone (); //TODO: check if UrhoObject var create = (sharedPtrType.ToString() == "AnimationState") ? "CreateVectorSharedPtrRefcountedProxy" : "CreateVectorSharedPtrProxy"; returnExpression = new ConditionalExpression ( new BinaryOperatorExpression (new IdentifierExpression (cacheName), BinaryOperatorType.InEquality, new PrimitiveExpression (null)), new IdentifierExpression (cacheName), new AssignmentExpression ( new IdentifierExpression (cacheName), new InvocationExpression ( new MemberReferenceExpression ( new IdentifierExpression ("Runtime"), create, sharedPtrType), invoke))); break; default: returnExpression = invoke; break; } if (ret != null) { ret.Expression = returnExpression; method.Body.Add(ret); } else { if (currentType.ClassType == ClassType.Class){ //usually, the Context is the first object user creates so let's add additional check if engine is inited if (currentType.Name == "Context") { constructor.Body.Add (new InvocationExpression (new IdentifierExpression ("CheckEngine"), null)); } bool hasBaseTypes = currentType.BaseTypes.Count != 0; if (hasBaseTypes) { constructor.Initializer = new ConstructorInitializer { ConstructorInitializerType = ConstructorInitializerType.Base, }; constructor.Initializer.Arguments.Add(csParser.ParseExpression("UrhoObjectFlag.Empty")); } var ctorAssign = new AssignmentExpression(new IdentifierExpression("handle"), returnExpression); constructor.Body.Add(new ExpressionStatement(ctorAssign)); if (hasBaseTypes) { constructor.Body.Add (new InvocationExpression (new MemberReferenceExpression (new IdentifierExpression ("Runtime"), "RegisterObject"), new ThisReferenceExpression ())); } } } var rstr = String.Format(marshalReturn, cinvoke.ToString()); CXXRecordDecl returnType; //Wrap with WeakPtr all RefCounted subclasses constructors if (isConstructor) { if (ScanBaseTypes.nameToDecl.TryGetValue(decl.Parent.QualifiedName, out returnType) && returnType.IsDerivedFrom(ScanBaseTypes.UrhoRefCounted)) rstr = $"WeakPtr<{decl.Name}>({rstr})"; } cmethodBuilder.AppendLine(!rstr.Contains("\treturn ") ? $"return {rstr};" : rstr); } cmethodBuilder.AppendLine("}\n"); var code = cmethodBuilder.ToString(); const string variantArgDef = "const class Urho3D::Variant &"; //if methods contains Variant argument -- replace it with overloads if (code.Contains(variantArgDef)) { var variantSupportedTypes = new Dictionary<string, string> { //C++ - C# types map {"const class Urho3D::Vector3 &", "Vector3"}, {"const class Urho3D::IntRect &", "IntRect"}, {"const class Urho3D::Color &", "Color"}, {"const class Urho3D::Vector2 &", "Vector2"}, {"const class Urho3D::Vector4 &", "Vector4"}, {"const class Urho3D::IntVector2 &", "IntVector2"}, {"const class Urho3D::Quaternion &", "Quaternion"}, {"int", "int"}, {"float", "float"}, {"const char *", "string"}, //TODO: Matrix, StringHash? }; var primitiveTypes = new[] { "int", "float", "string" }; pn("// Urho3D::Variant overloads begin:"); int index = 0; foreach (var item in variantSupportedTypes) { //C: p(code .Replace(variantArgDef, item.Key) .Replace(methodNameSuffix, index.ToString()) .Replace(variantConverterMask, item.Key == "const char *" ? "Urho3D::String" : string.Empty)); //methodNameSuffix to avoid error: // error C2733: second C linkage of overloaded function 'function name' not allowed. //C#: var isPrimitive = primitiveTypes.Contains(item.Value); AstType argumentType; var argumentModifier = ICSharpCode.NRefactory.CSharp.ParameterModifier.None; if (!isPrimitive) { argumentType = new SimpleType(item.Value); argumentModifier = ICSharpCode.NRefactory.CSharp.ParameterModifier.Ref; } else { argumentType = new PrimitiveType(item.Value); } var dllImportItem = (MethodDeclaration)pinvoke.Clone(); var originalEntryPointName = dllImportItem.Name; dllImportItem.Name += index; var variantParam = dllImportItem.Parameters.First(p => p.ToString().Contains(variantArgDef)); variantParam.Type = argumentType.Clone(); variantParam.ParameterModifier = argumentModifier; currentType.Members.Add(dllImportItem); var clonedMethod = (MethodDeclaration)method.Clone(); variantParam = clonedMethod.Parameters.First(p => p.ToString().Contains(variantArgDef)); variantParam.Type = argumentType.Clone(); //add 'index' to all EntryPoint invocations inside the method (no mater how complex method body is): //and 'ref' keyword for the argument clonedMethod.Body.Descendants .OfType<InvocationExpression>() .Where(ie => ie.Target is IdentifierExpression && ((IdentifierExpression)ie.Target).Identifier == originalEntryPointName) .ToList() .ForEach(ie => { if (!isPrimitive) { //non-primitive types should be marked with 'ref' keyword var argument = ie.Arguments.OfType<IdentifierExpression>().First(arg => arg.Identifier == variantParam.Name); ie.Arguments.Remove(argument); ie.Arguments.Add(new DirectionExpression(FieldDirection.Ref, argument)); } var exp = (IdentifierExpression)ie.Target; exp.Identifier += index; }); currentType.Members.Add(clonedMethod); InsertSummaryComments(clonedMethod, StringUtil.GetMethodComments(decl)); index++; } pn("// Urho3D::Variant overloads end."); } //method does not have "Variant" arguments else { //C: pn(code .Replace(methodNameSuffix, string.Empty) .Replace(variantConverterMask, string.Empty)); //C#: currentType.Members.Add(pinvoke); if (method == null) currentType.Members.Add(constructor); else { currentType.Members.Add(method); InsertSummaryComments(method, StringUtil.GetMethodComments(decl)); } } }
public override void VisitObjCInterfaceDecl(ObjCInterfaceDecl decl, VisitKind visitKind) => VisitItem(decl, visitKind);