Skip to content

Commit

Permalink
Revisit indexing operators and fix leaks and crashes (#548)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
elijah-semyonov authored Sep 27, 2024
1 parent 06b9fba commit 55637fe
Show file tree
Hide file tree
Showing 6 changed files with 523 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>2.190000</real>
<real>0.223000</real>
<key>baselineIntegrationDisplayName</key>
<string>Local Baseline</string>
</dict>
Expand All @@ -21,7 +21,7 @@
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>4.504656</real>
<real>0.446000</real>
<key>baselineIntegrationDisplayName</key>
<string>Local Baseline</string>
</dict>
Expand All @@ -31,7 +31,7 @@
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>3.270000</real>
<real>0.332172</real>
<key>baselineIntegrationDisplayName</key>
<string>Local Baseline</string>
</dict>
Expand Down
38 changes: 20 additions & 18 deletions Generator/Generator/BuiltinGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -508,28 +508,30 @@ func generateBuiltinMethods (_ p: Printer,
p.staticVar (visibility: "private ", name: "keyed_checker", type: "GDExtensionPtrKeyedChecker") {
p ("return gi.variant_get_ptr_keyed_checker (\(variantType))!")
}
p ("public subscript (key: Variant) -> Variant?") {
p ("get") {
p ("let keyCopy = key")
p ("var result = Variant.zero")
p ("if Self.keyed_checker (&content, &keyCopy.content) != 0") {
p ("Self.keyed_getter (&content, &keyCopy.content, &result)")
p ("return Variant(copying: result)")
}
p ("else") {
p ("return nil")
p("""
public subscript(key: Variant) -> Variant? {
get {
var result = Variant.zero
if Self.keyed_checker(&content, &key.content) != 0 {
Self.keyed_getter (&content, &key.content, &result)
// Returns unowned handle
return Variant(takingOver: result)
} else {
return nil
}
}
p ("set") {
p ("let keyCopy = key")
p ("if let newCopy = newValue") {
p ("Self.keyed_setter (&content, &keyCopy.content, &newCopy.content)")
}
p ("else") {
p ("Self.keyed_setter (&content, &keyCopy.content, nil)")
}
set {
if let newValue {
Self.keyed_setter(&content, &key.content, &newValue.content)
} else {
var nilContent = Variant.zero
// nil will cause a crash, needs a pointer to Nil Variant content instead
Self.keyed_setter(&content, &key.content, &nilContent)
}
}
}
""")
}
if let returnType = bc.indexingReturnType, !bc.isKeyed, !bc.name.hasSuffix ("Array"), bc.name != "String" {
let godotType = getGodotType (JGodotReturnValue (type: returnType, meta: nil))
Expand Down
13 changes: 12 additions & 1 deletion Sources/SwiftGodot/Core/GArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,25 @@ extension GArray {
return Variant()
}
let ptr = ret.assumingMemoryBound(to: Variant.ContentType.self)

// We are making a copy of the variant managed by the array. Array is managing its copy, we are managing ours
return Variant(copying: ptr.pointee)
}
set {
guard let ret = gi.array_operator_index (&content, Int64 (index)) else {
return
}
let ptr = ret.assumingMemoryBound(to: Variant.ContentType.self)
ptr.pointee = newValue.content

guard ptr.pointee != newValue.content else {
return
}

// We are taking the variant from the array at the `index` and assuming control over it. Since we don't need it, we just destroy it
gi.variant_destroy(ptr)

// We are giving array a copy of `newValue` Variant to manage
gi.variant_new_copy(ptr, &newValue.content)
}
}

Expand Down
3 changes: 2 additions & 1 deletion Sources/SwiftGodot/Variant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ public class Variant: Hashable, Equatable, CustomDebugStringConvertible {
if valid == 0 || oob != 0 {
return nil
}
return Variant(copying: _result)

return Variant(takingOver: _result)
}
set {
guard let newValue else {
Expand Down
6 changes: 3 additions & 3 deletions Tests/SwiftGodotTests/MarshalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ final class MarshalTests: GodotTestCase {
let child = TestVariant()

measure {
for _ in 0..<10000000 {
for _ in 0..<1_000_000 {
tv.addChild(node: child)
tv.removeChild(node: child)
}
Expand All @@ -58,7 +58,7 @@ final class MarshalTests: GodotTestCase {
let weight = 0.23

measure {
for _ in 0..<10000000 {
for _ in 0..<1_000_000 {
let _ = a.cubicInterpolate(b: b, preA: preA, postB: preB, weight: weight)
}
}
Expand All @@ -68,7 +68,7 @@ final class MarshalTests: GodotTestCase {
let randomValues = (0..<10).map { _ in Variant(Float.random(in: -1.0...1.0)) }

measure {
for _ in 0..<1000000 {
for _ in 0..<100_000 {
let _ = GD.max(arg1: randomValues[0], arg2: randomValues[1], randomValues[2], randomValues[3], randomValues[4], randomValues[5], randomValues[6], randomValues[7], randomValues[8], randomValues[9])
}
}
Expand Down
Loading

0 comments on commit 55637fe

Please sign in to comment.