← Async Digital

English Cymraeg

Nodiadau gwaith ·

Cuddiodd un galwr y chwalfa

Async Digital Ltd Caerdydd, DU

Crynodeb

Pasiodd un o’m cyfresi profion Swift saith allan o saith tra oedd y cod oddi tani yn llygru’r dyrannwr tasgau ar bob rhediad. Roedd y diffyg yn un go iawn, yn digwydd bob tro, ac yn y cod sy’n cael ei gludo. Roedd yn anweledig am mai un ffeil yn unig yn y gyfres oedd yn galw’r cychwynnydd sy’n ei sbarduno, ac mae angen dwy arno cyn y gall fodoli o gwbl. Trodd ychwanegu ail ffeil brawf sy’n gwneud yr un alwad yr un gyfres yn goch, dau allan o ddau, yn erbyn llyfrgell nad oedd wedi newid. Mae’r nam offer wedi ei ffeilio fel swiftlang/swift#92017. Canlyniad y prawf yw’r rhan rwy’n dymuno ei chadw.

Rwyf wedi treulio’r flwyddyn hon yn ysgrifennu am yr hyn sy’n torri pan roddwch waith go iawn i asiantau AI. Nid yw’r nodyn hwn am asiantau. Mae am gyfres brofion werdd na allai fod wedi troi’n goch, sef yr un siâp â llawer o’r hyn rwy’n ei ysgrifennu yma, ac fe’i cefais mewn Swift plaen.

§1·Terfynu

Y neges nad chi sydd i fod i’w hachosi

Mae gen i becyn bach sy’n cyfresoli elfennau UI sy’n cystadlu â’i gilydd, y math lle mae dolen ddofn, rhybudd a baner cysylltedd i gyd eisiau’r sgrin ar yr un pryd. Dechreuodd derfynu o dan brawf gyda neges gan amser rhedeg Swift: freed pointer was not the last allocation.

Daw honno o swift_task_dealloc, sef yr amser rhedeg cydredolrwydd yn datgymalu ffrâm anghydamserol. Nid yw fy nghod i yn ei alw. Felly naill ai roeddwn wedi dod o hyd i nam yn yr offer, sef yr eglurhad y dylech ei amau gyntaf ac yn galetaf, neu roeddwn wedi gwneud rhywbeth i’w haeddu.

Y cyntaf oedd hi. Mae’r dystiolaeth isod, a hi yw hanner llai diddorol y nodyn hwn.

§2·Rhagosodiad

Dau gopi o un closure

Mae gan y pecyn reolwr sy’n oedi rhwng eitemau mewn ciw. Caiff y cwsg ei chwistrellu er mwyn i brofion allu ei yrru â chloc ffug, ac roedd y cloc go iawn yn eistedd mewn arg rhagosodedig:

public init(sleep: @escaping @Sendable (TimeInterval) async throws -> Void
            = { try await Task.sleep(for: .seconds($0)) }) {
    self.sleep = sleep
}

Siâp cyffredin yw hwnnw. Dyna hefyd y nam cyfan.

Pasiodd pob prawf oedd yn chwistrellu cwsg. Pasiodd rhoi’r un closure yn benodol wrth y pwynt galw, gydag amser go iawn ar y cloc. Pasiodd Task.sleep(nanoseconds:). Pasiodd ffwythiant anghydamserol rhydd. Dim ond y fersiwn arg rhagosodedig a derfynodd, a gwnaeth hynny ar bob ailadeiladu, deg gwaith allan o ddeg. Felly bod yn arg rhagosodedig yw’r gwahaniaethydd, nid cysgu.

Caiff mynegiadau arg rhagosodedig ffwythiannau cyhoeddus eu cydosod i mewn i’r galwr. Mae’r closure felly’n bodoli ddwywaith: unwaith yn y llyfrgell sy’n ei ddatgan, ac unwaith ym mhob modiwl sy’n mewnforio’r llyfrgell ac yn defnyddio’r rhagosodiad. Caiff y ddau gopi eu hallyrru fel symbolau unwaith-yn-unig, ac mae gan y cysylltydd hawl i gadw’r naill neu’r llall.

O dan -Onone roedd gan y ddau gopi gynlluniau ffrâm anghydamserol gwahanol. Defnyddiodd copi’r llyfrgell gyd-destun 128 beit a gollwng y gofrestr gwallau at offset 0x70. Defnyddiodd copi’r cleient 112 beit a gollwng yr un gofrestr at 0x68, gyda phob maes yn eistedd wyth beit yn is. Dau gynllun, nid un cynllun gyda bwlch ynddo.

Cymerodd y cysylltydd y corff o wrthrych y llyfrgell, a’r pwyntydd ffwythiant anghydamserol, sef y cofnod sy’n cario maint y cyd-destun, o wrthrych y cleient. Roedd pob galwad wedyn yn dyrannu 112 beit ar gyfer cod sy’n ysgrifennu at 0x70. Ac mae 0x70 yn 112, felly glaniodd yr ysgrifen union un gair y tu hwnt i ddiwedd y dyraniad.

§3·Prawf

Dal yr ysgrifen wrth y weithred

Yr hyn sy’n eistedd un gair y tu hwnt i’r diwedd yw pennyn y dyraniad tasg nesaf. Cadarnheais hynny yn hytrach na’i gasglu, gyda rhyng-osodwr DYLD_INSERT_LIBRARIES ar swift_task_alloc a swift_task_dealloc a wiriodd bob pennyn wrth iddo fynd heibio.

Yn y dasg sy’n chwalu, y drefn yw cyd-destun oedi 112 beit, cyd-destun closure 112 beit, byffer cloc dim beit, yna cyd-destun Task.sleep 32 beit. Mae pennyn y byffer dim beit yn eistedd wrth gyd-destun y closure plws 0x70. Roedd ei ddolen flaenorol yn gywir pan gafodd ei dyrannu ac yn sero erbyn i’r closure ryddhau cyd-destun y cwsg. Mae’r amser rhedeg yn terfynu wrth y dadddyrannu nesaf am nad yw’r rhestr rydd yn gwneud synnwyr mwyach.

Un gwiriad arall, am fod mecanwaith na allwch ond ei ddisgrifio yn fecanwaith y gallech fod wedi ei ddychmygu. Ailgysylltais yr union wrthrychau gyda’r rhestr ffeiliau o chwith. Cymerodd y cysylltydd wedyn gorff y cleient ynghyd â chofnod y cleient, cytunodd y ddau, a phasiodd pob prawf oedd wedi bod yn chwalu. Yr un cydosodwr, yr un gwrthrychau, trefn wahanol.

Roedd adeiladau rhyddhau yn wyrdd drwyddi draw. Mae’r ddau gopi yn optimeiddio i lawr i ffrâm 64 beit, mae’r cofnodion yn cytuno, ac mae’r diffyg wedi mynd. Mae’n werth ei ddweud yn glir: ni fyddai gwaith CI sy’n rhedeg ar adeiladau rhyddhau yn unig byth wedi gweld hyn.

§4·Dall

Y prawf a adeiladwyd i ddal hyn, ac na allai

Dyma’r sefyllfa roeddwn ynddi mewn gwirionedd cyn i ddim o hynny ddechrau.

Roedd saith prawf yn y gyfres a phasiodd pob un. Roedd un ohonynt yn brawf atchweliad cloc go iawn a oedd yn bodoli’n benodol i ymarfer y cychwynnydd rhagosodedig, hynny yw, roedd yn bodoli i ddal y dosbarth hwn o ddiffyg. Pasiodd hwnnw hefyd, a phasiodd yn onest. Roedd y deuaidd y rhedodd yn ei erbyn wedi ei gysylltu’n lân.

Cafodd ei gysylltu’n lân am mai hwnnw oedd yr unig gleient. Gydag un gwrthrych mewnforio yn defnyddio’r arg rhagosodedig, mae’r cysylltydd yn cymryd y corff a chofnod y ffrâm o’r un lle, mae’r ddau yn cytuno, ac nid oes dim i beidio â chyfateb. Mae angen dau wrthrych mewnforio ar y nam cyn iddo fodoli.

Felly ychwanegais ail ffeil brawf sydd hefyd yn galw’r cychwynnydd rhagosodedig ac nad yw’n gwneud dim arall o bwys. Terfynodd yr un gyfres, yn erbyn yr un llyfrgell heb ei thrwsio, ddau allan o ddau. Ar ôl y trwsiad pasiodd wyth allan o wyth.

Nid prawf yw’r cynhwysyn. Ail alwr ydyw. Gall cyfres ddal prawf a ysgrifennwyd i ddal diffyg, ei redeg ar bob ymrwymiad am fisoedd, a bod yn strwythurol analluog i’w gyrraedd. Nid oedd dim yn wan am y prawf hwnnw. Cafodd ei lwgu, sef yr un peth yn union ag a welais mewn piblinell gof bythefnos yn ôl ac nad oeddwn yn disgwyl ei gyfarfod eto mewn cysylltydd.

§5·Maglau

Dau beth llai a ddywedodd ddim wrthyf

Rhedodd y rhaglen arddangos yn iawn drwy’r amser. Mae’n cario’r un anghyfatebiaeth ac yn gorlifo un gair ar bob cwsg go iawn. Yn ei chynllun dyrannu hi, mae’r gorlif yn glanio ar dir rhydd yn hytrach nag ar bennyn byw, felly nid oes dim yn cwyno. Nid tystiolaeth oedd hynny fod y llyfrgell yn iach. Llygredd tawel gydag wyneb cyfeillgar ydoedd, ac roeddwn wedi bod yn ei ddarllen fel rheolydd positif.

Roedd nifer o’m pasiau cynnar fy hun yn wag. Mae adeiladu’r rheolwr fel gwrthrych dros dro ac anfon arno yn gadael i’r rheolwr gael ei ddadleoli cyn i’r dasg redeg. Mae’r cipiad gwan yn nil, nid oes dim yn cysgu, ac mae’r prawf yn adrodd llwyddiant mewn tua milieiliad. Nid yw prawf sy’n pasio mewn 1 ms pan fo’r gwaith yn cymryd 25 ms wedi pasio. Ailredais bopeth gan ddal y rheolwr a mynnu bod yr amser a aeth heibio yn cael ei brofi.

§6·Cadw

Y trwsiad, a’r peth rwy’n cael ei gadw

Gollyngwch y rhagosodiad a rhowch y closure y tu mewn i’r llyfrgell:

public init(sleep: @escaping @Sendable (TimeInterval) async throws -> Void) {
    self.sleep = sleep
}
public convenience init() {
    self.init(sleep: { try await Task.sleep(for: .seconds($0)) })
}

Un copi o’r closure sy’n bodoli, felly nid oes dim i beidio â chyfateb, ac mae’r pwyntiau galw heb newid. Mae’r rheol rwy’n gweithio iddi bellach yn gulach nag y mae’n swnio: dim closures llythrennol yn args rhagosodedig cyhoeddus gwnïau anghydamserol.

I unrhyw un sy’n cyrraedd yma ar ôl chwilio am neges y terfyniad, y siâp sbarduno yw cychwynnydd cyhoeddus ar ddosbarth generig, lle mae ei arg rhagosodedig yn closure anghydamserol llythrennol, wedi ei alw o fodiwl arall, mewn adeiladwaith dadfygio. Mae wedi ei ffeilio fel swiftlang/swift#92017. Mae dau adroddiad sy’n bodoli eisoes, #86204 a #88794, yn dangos yr un terfyniad heb ddiagnosis, sy’n awgrymu nad yw’r siâp yn brin.

Caiff y nam ei drwsio gan bobl nad ydynt yn fi. Yr hyn rwy’n cael ei gadw yw canlyniad y prawf, a chanlyniad y prawf oedd nad oedd cyfres werdd yn dweud dim wrthyf, am reswm heb unrhyw gysylltiad ag ansawdd y prawf.

Rwy’n gweithio i reol bellach ynghylch unrhyw beth rwyf wedi ei osod i ddal rhywbeth: nid yw gwiriad sy’n pasio yn wybodaeth oni bai fy mod wedi gweld y gwiriad hwnnw’n methu. Nid oeddwn erioed wedi gweld y prawf hwnnw’n troi’n goch. Petawn wedi ceisio unwaith, yn fwriadol, i’w wneud yn fethiant, byddwn wedi darganfod mewn prynhawn na allai.

Dull sut y gwnaed y nodyn hwn

Fy stori i yw hon a digwyddodd. Mae’r cynlluniau, yr offsets a’r cyfrifon yn §2 a §3 wedi eu darllen oddi ar fy ngwrthrychau a’m deuaidd fy hun ar Swift 6.2.4 (swiftlang-6.2.4.1.4), Xcode 26.3, ld-1230.1, macOS 26.5.2, arm64. Adeiladau dadfygio yn unig.

Mae’r adroddiad i fyny’r afon yn cario atgynhyrchiad heb ddim o’m cod i ynddo, felly gellir gwirio’r mecanwaith heb gymryd fy ngair am y pecyn lle’i cafwyd.

Gweithiais ar hyn gydag asiant AI, fel y gwnaf ar y rhan fwyaf o bethau bellach. Fi oedd i ofyn am y gwiriad ailgysylltu a’r ail brawf galwr; nid oedd darllen y dadgydosodiad yn rhywbeth y byddwn wedi ei wneud â llaw.