Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variant crash #531

Closed
migueldeicaza opened this issue Aug 27, 2024 · 4 comments
Closed

Variant crash #531

migueldeicaza opened this issue Aug 27, 2024 · 4 comments

Comments

@migueldeicaza
Copy link
Owner

The following example that implements an encoding for Godot crashes, due to attempting to access freed information:

//
//  main.swift
//
// Very trivial example of using SwiftGodotKit
//
//  Created by Miguel de Icaza on 4/1/23.
//

//import Foundation
import SwiftGodot
import SwiftGodotKit

struct Foo: Codable {
    var myInt: Int
    var myText: String
    var myIntArray: [Int]
}

struct FooN: Codable {
    var myInt: Int
    var myText: String
    var myFoo: [Foo]
}

protocol GodotEncodingContainer {
    var data: Variant { get }
}

extension Vector2: Codable {
    enum CodingKeys: String, CodingKey {
        case x
        case y
    }

    public init (from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let x = try values.decode(Float.self, forKey: .x)
        let y = try values.decode(Float.self, forKey: .y)
        self.init (x: x, y: y)
    }

    public func encode(to encoder: any Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(x, forKey: .x)
        try container.encode(x, forKey: .y)
    }
}

/// Notes on encoding:
///   - Int8 is encoded as an Int
public class GodotEncoder: Encoder {
    public var codingPath: [any CodingKey] = []
    public var userInfo: [CodingUserInfoKey : Any] = [:]
    var container: GodotEncodingContainer? = nil

    public init () {

    }

    public func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {

        var container = GodotKeyedContainer<Key>(codingPath: codingPath, userInfo: userInfo)
        self.container = container
        return KeyedEncodingContainer(container)
    }

    public var data: Variant {
        return container?.data ?? Variant()
    }

    func encode(key codingKey: [CodingKey], value: Variant) {
        let key = codingKey.map { $0.stringValue }.joined(separator: ".")
        fatalError()
        //dict [key] = value
    }

    public func unkeyedContainer() -> any UnkeyedEncodingContainer {
        let container = GodotUnkeyedContainer(codingPath: codingPath, userInfo: userInfo)
        self.container = container
        return container
    }

    public func singleValueContainer() -> any SingleValueEncodingContainer {
        let container = GodotSingleValueContainer(codingPath: codingPath, userInfo: userInfo)
        self.container = container
        return container
    }

    class GodotKeyedContainer<Key:CodingKey>: KeyedEncodingContainerProtocol, GodotEncodingContainer {
        func encodeNil(forKey key: Key) throws {
            var container = self.nestedSingleValueContainer(forKey: key)
            fatalError()
            //try container.encode(Variant())
        }

        var codingPath: [any CodingKey] = []
        var userInfo: [CodingUserInfoKey: Any]
        var storage: [String:GodotEncodingContainer] = [:]

        var data: Variant {
            let dict = GDictionary()
            for (k,v) in storage {
                dict [k] = Variant(v.data)
            }
            return Variant(dict)
        }

        init (codingPath: [any CodingKey], userInfo: [CodingUserInfoKey: Any]) {
            self.codingPath = codingPath
            self.userInfo = userInfo
        }

        func encode(_ value: String, forKey key: Key) throws {
            var container = self.nestedSingleValueContainer(forKey: key)
            try container.encode(value)
            self.storage[key.stringValue] = container
        }

        func encode(_ value: Int, forKey key: Key) throws {
            var container = self.nestedSingleValueContainer(forKey: key)
            try container.encode(value)
            self.storage[key.stringValue] = container
        }

        func nestedSingleValueContainer(forKey key: Key)
            -> GodotSingleValueContainer
        {
            let container = GodotSingleValueContainer(
                codingPath: self.codingPath + [key],
                userInfo: self.userInfo
            )
            return container
        }

        func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
            var container = self.nestedSingleValueContainer(forKey: key)
            try container.encode(value)
            self.storage[key.stringValue] = container

        }

        func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
            fatalError()
        }

        func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer {
            fatalError()
        }

        func superEncoder() -> any Encoder {
            fatalError()
        }

        func superEncoder(forKey key: Key) -> any Encoder {
            fatalError()
        }
    }

    class GodotUnkeyedContainer: UnkeyedEncodingContainer, GodotEncodingContainer {
        var codingPath: [any CodingKey] = []
        var userInfo: [CodingUserInfoKey: Any]

        var storage = GArray()

        var data: Variant {
            return Variant(storage)
        }

        init (codingPath: [any CodingKey], userInfo: [CodingUserInfoKey: Any]) {
            self.codingPath = codingPath
            self.userInfo = userInfo
        }

        var count: Int {
            Int(storage.size())
        }

        func encode(_ value: String) throws {
            fatalError()
        }

        func encode(_ value: Double) throws {
            fatalError()
        }

        func encode(_ value: Float) throws {
            fatalError()
        }

        func encode(_ value: Int) throws {
            fatalError()
        }

        func encode(_ value: Int8) throws {
            fatalError()
        }

        func encode(_ value: Int16) throws {
            fatalError()
        }

        func encode(_ value: Int32) throws {
            fatalError()
        }

        func encode(_ value: Int64) throws {
            fatalError()
        }

        func encode(_ value: UInt) throws {
            fatalError()
        }

        func encode(_ value: UInt8) throws {
            fatalError()
        }

        func encode(_ value: UInt16) throws {
            fatalError()
        }

        func encode(_ value: UInt32) throws {
            fatalError()
        }

        func encode(_ value: UInt64) throws {
            fatalError()
        }

        func encode<T>(_ value: T) throws where T : Encodable {
            let base = Int(storage.size())


            if let v = value as? Array<Encodable> {
                _ = storage.resize(size: storage.size () + Int64(v.count))
                for i in 0..<v.count {
                    let v = v [i]
                    let nested = GodotSingleValueContainer(codingPath: codingPath, userInfo: userInfo)
                    try nested.encode(v)
                    storage [base+i] = nested.data
                }
            } else {
                _ = storage.resize(size: storage.size () + 1)
                let nested = GodotSingleValueContainer(codingPath: codingPath, userInfo: userInfo)
                try nested.encode (value)
                storage [base] = nested.data
            }
        }

        func encode(_ value: Bool) throws {
            fatalError()
        }

        private struct IndexedCodingKey: CodingKey {
            let intValue: Int?
            let stringValue: String

            init?(intValue: Int) {
                self.intValue = intValue
                self.stringValue = intValue.description
            }

            init?(stringValue: String) {
                return nil
            }
        }

        func encodeNil() throws {
            fatalError()
        }

        func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
            fatalError()
        }

        func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer {
            fatalError()
        }

        func superEncoder() -> any Encoder {
            fatalError()
        }


    }

    class GodotSingleValueContainer: SingleValueEncodingContainer, GodotEncodingContainer {
        func encode<T>(_ value: T) throws where T : Encodable {
            if value is Array<Any> {
                var container = GodotUnkeyedContainer (codingPath: codingPath, userInfo: userInfo)
                try container.encode(value)
                self.value = container.data
            } else if let i = value as? Int {
                try self.encode (i)
            } else if let str = value as? String {
                try self.encode (str)
            } else {
                var nested = GodotEncoder()
                try value.encode(to: nested)
                self.value = nested.container?.data
            }
        }

        enum foo: Error {
            case nope
        }

        var codingPath: [any CodingKey] = []
        var userInfo: [CodingUserInfoKey: Any]
        var value: Variant?

        var data: Variant {
            value ?? Variant()
        }

        init (codingPath: [any CodingKey], userInfo: [CodingUserInfoKey: Any]) {
            self.codingPath = codingPath
            self.userInfo = userInfo
        }

        func encodeNil() throws {
            fatalError()
        }

        func encode(_ value: Bool) throws {
            fatalError()
        }

        func encode(_ value: String) throws {
            self.value = Variant(value)
        }

        func encode(_ value: Double) throws {
            fatalError()
        }

        func encode(_ value: Float) throws {
            fatalError()
        }

        func encode(_ value: Int) throws {
            self.value = Variant(Int(value))
        }

        func encode(_ value: Int8) throws {
            fatalError()
        }

        func encode(_ value: Int16) throws {
            fatalError()
        }

        func encode(_ value: Int32) throws {
            fatalError()
        }

        func encode(_ value: Int64) throws {
            fatalError()
        }

        func encode(_ value: UInt) throws {
            fatalError()
        }

        func encode(_ value: UInt8) throws {
            fatalError()
        }

        func encode(_ value: UInt16) throws {
            fatalError()
        }

        func encode(_ value: UInt32) throws {
            fatalError()
        }

        func encode(_ value: UInt64) throws {
            fatalError()
        }
    }
}


var last: Node? = nil
func loadScene (scene: SceneTree) {
    let g = GodotEncoder()
    let foon = FooN(myInt: 9, myText: "nine", myFoo: [
        Foo (myInt: 10, myText: "Nested1", myIntArray: [1,1,1]),
        Foo (myInt: 30, myText: "Nested2", myIntArray: [2,2,2])])
    try? foon.encode (to:g)
    print (g.data.description)
}

func registerTypes (level: GDExtension.InitializationLevel) {
}

runGodot(args: [], initHook: registerTypes, loadScene: loadScene, loadProjectSettings: { settings in })
@TCROC

This comment was marked as off-topic.

@migueldeicaza

This comment was marked as off-topic.

migueldeicaza pushed a commit that referenced this issue Sep 27, 2024
* Fixes #543 (Godot String via Variant subscript operator leaks) and #531 (Variant crash)

* Reduce performance checks iterations x10.

* Fix dictionary leaks and a crash when dictionary[key] = nil
@elijah-semyonov
Copy link
Contributor

elijah-semyonov commented Sep 28, 2024

@migueldeicaza
Fixed too, I guess?

@migueldeicaza
Copy link
Owner Author

Thanks! Closing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants