diff --git a/docs/libreoffice-worklist.txt b/docs/libreoffice-worklist.txt new file mode 100644 index 000000000..874da0c96 --- /dev/null +++ b/docs/libreoffice-worklist.txt @@ -0,0 +1,165 @@ + +== STUBBED C FUNCTIONS THE APPLICATION IMPORTS: 24 + CGContextShowGlyphsAtPositions [vendor/src/cocotron/CoreGraphics/CGContext.m] + CGDisplayScreenSize [vendor/src/cocotron/CoreGraphics/CGDirectDisplay.m] + CGFontCreateWithDataProvider [vendor/src/cocotron/CoreGraphics/CGFont.m] + CGImageSourceCreateWithDataProvider [src/darwin/frameworks/ImageIO/src/CGImageSource.m] + CTFontCopyAttribute [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCopyCharacterSet [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCopyFamilyName [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCopyFontDescriptor [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCopyGraphicsFont [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCopyLocalizedName [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCopyPostScriptName [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCopyVariation [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCopyVariationAxes [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCreateCopyWithAttributes [vendor/src/cocotron/CoreText/CTFont.m] + CTFontCreateCopyWithSymbolicTraits [vendor/src/cocotron/CoreText/CTFont.m] + CTFontDescriptorCreateWithAttributes [vendor/src/cocotron/CoreText/stubs.c] + CTFontDrawGlyphs [vendor/src/cocotron/CoreText/CTFont.m] + CTFontGetBoundingRectsForGlyphs [vendor/src/cocotron/CoreText/CTFont.m] + CTFontGetSymbolicTraits [vendor/src/cocotron/CoreText/CTFont.m] + CTFontGetUnitsPerEm [vendor/src/cocotron/CoreText/CTFont.m] + CTFontManagerCopyAvailableFontFamilyNames [vendor/src/cocotron/CoreText/CTFontManager.m] + CTFontManagerRegisterFontsForURL [vendor/src/cocotron/CoreText/CTFontManager.m] + NSAccessibilityUnignoredChildren [vendor/src/cocotron/AppKit/NSAccessibility.m] + NSSetFocusRingStyle [vendor/src/cocotron/AppKit/NSGraphics.m] + +== STUBBED METHODS WHOSE SELECTOR THE APPLICATION USES: 74 + CGColor [vendor/src/cocotron/AppKit/NSColor.subproj/NSColor.m] + acceptsFirstMouse: [vendor/src/cocotron/AppKit/NSView.m] + activateIgnoringOtherApps: [vendor/src/cocotron/AppKit/NSApplication.m] + appearanceNamed: [vendor/src/cocotron/AppKit/NSAppearance.m] + bitmapImageRepForCachingDisplayInRect: [vendor/src/cocotron/AppKit/NSView.m] + cacheDisplayInRect:toBitmapImageRep: [vendor/src/cocotron/AppKit/NSView.m] + cellSize [vendor/src/cocotron/AppKit/NSTextAttachmentCell.m] + changeCount [vendor/src/cocotron/AppKit/NSPasteboard.m] + characterIndexForPoint: [vendor/src/cocotron/AppKit/NSTextView.subproj/NSTextView.m] + colorUsingColorSpaceName:device: [vendor/src/cocotron/AppKit/NSColor.subproj/NSColor.m] + currentContext [vendor/src/cocotron/AppKit/NSAnimation/NSAnimationContext.m] + dataForType: [vendor/src/cocotron/AppKit/NSPasteboard.m] + declareTypes:owner: [vendor/src/cocotron/AppKit/NSPasteboard.m] + displayRectIgnoringOpacity:inContext: [vendor/src/cocotron/AppKit/NSView.m] + dragImage:at:offset:event:pasteboard:source:slideBack: [vendor/src/cocotron/AppKit/NSDraggingManager.m] + drawStatusBarBackgroundInRect:withHighlight: [vendor/src/cocotron/AppKit/NSStatusItem.m] + drawWithFrame:inView: [vendor/src/cocotron/AppKit/NSTextAttachmentCell.m] + drawableSize [vendor/src/cocotron/QuartzCore/CAMetalLayer.mm] + duration [vendor/src/cocotron/AppKit/NSAnimation/NSAnimationContext.m] + font [vendor/src/cocotron/AppKit/NSText.m] + framebufferOnly [vendor/src/cocotron/QuartzCore/CAMetalLayer.mm] + getRed:green:blue:alpha: [vendor/src/cocotron/AppKit/NSColor.subproj/NSColor.m] + hideOtherApplications: [vendor/src/cocotron/AppKit/NSApplication.m] + highlightWithLevel: [vendor/src/cocotron/AppKit/NSColor.subproj/NSColor_catalog.m] + isEnabled [vendor/src/cocotron/AppKit/NSFontManager.m] + isOpaque [vendor/src/cocotron/AppKit/NSSplitView.m] + isValid [vendor/src/cocotron/AppKit/NSImage.m] + maxSize [vendor/src/cocotron/AppKit/NSText.m] + menu [vendor/src/cocotron/AppKit/NSResponder.m] + minSize [vendor/src/cocotron/AppKit/NSText.m] + mouseEntered: [vendor/src/cocotron/AppKit/NSButtonCell.m] + mouseExited: [vendor/src/cocotron/AppKit/NSButtonCell.m] + mouseLocation [vendor/src/cocotron/AppKit/NSDisplay.m] + name [vendor/src/cocotron/AppKit/NSPasteboard.m] + nextDrawable [vendor/src/cocotron/QuartzCore/CAMetalLayer.mm] + objectEnumerator [vendor/src/cocotron/CoreData/NSManagedObjectMutableSet.m] + pasteboardWithName: [vendor/src/cocotron/AppKit/NSDisplay.m] + pause [vendor/src/cocotron/AppKit/NSSound.m] + pixelFormat [vendor/src/cocotron/QuartzCore/CAMetalLayer.mm] + play [vendor/src/cocotron/AppKit/NSSound.m] + reset [vendor/src/cocotron/CoreData/NSManagedObjectContext.m] + screens [vendor/src/cocotron/AppKit/NSDisplay.m] + selectedRange [vendor/src/cocotron/AppKit/NSText.m] + sendEvent: [vendor/src/cocotron/AppKit/NSWindow.m] + setAccessoryView: [vendor/src/cocotron/AppKit/NSColorPanel.m] + setAllowsOtherFileTypes: [vendor/src/cocotron/AppKit/NSSavePanel.subproj/NSSavePanel.m] + setBackgroundColor: [vendor/src/cocotron/AppKit/NSText.m] + setCanCreateDirectories: [vendor/src/cocotron/AppKit/NSSavePanel.subproj/NSSavePanel.m] + setColorspace: [vendor/src/cocotron/QuartzCore/CAMetalLayer.mm] + setData:forType: [vendor/src/cocotron/AppKit/NSPasteboard.m] + setDelegate: [vendor/src/cocotron/AppKit/NSText.m] + setDepthLimit: [vendor/src/cocotron/AppKit/NSWindow.m] + setDevice: [vendor/src/cocotron/QuartzCore/CAMetalLayer.mm] + setDisplaySyncEnabled: [vendor/src/cocotron/QuartzCore/CAMetalLayer.mm] + setDrawableSize: [vendor/src/cocotron/QuartzCore/CAMetalLayer.mm] + setDrawsBackground: [vendor/src/cocotron/AppKit/NSText.m] + setEditable: [vendor/src/cocotron/AppKit/NSText.m] + setFont: [vendor/src/cocotron/AppKit/NSText.m] + setImage: [vendor/src/cocotron/AppKit/NSBrowserCell.m] + setMaxSize: [vendor/src/cocotron/AppKit/NSText.m] + setMenu: [vendor/src/cocotron/AppKit/NSResponder.m] + setMinSize: [vendor/src/cocotron/AppKit/NSText.m] + setName: [vendor/src/cocotron/AppKit/NSSound.m] + setPixelFormat: [vendor/src/cocotron/QuartzCore/CAMetalLayer.mm] + setSelectable: [vendor/src/cocotron/AppKit/NSText.m] + sizeToFit [vendor/src/cocotron/AppKit/NSBox.m] + stop [vendor/src/cocotron/AppKit/NSSound.m] + string [vendor/src/cocotron/AppKit/NSText.m] + textColor [vendor/src/cocotron/AppKit/NSText.m] + type [vendor/src/cocotron/CoreData/NSPersistentStore.m] + types [vendor/src/cocotron/AppKit/NSPasteboard.m] + unhideAllApplications: [vendor/src/cocotron/AppKit/NSApplication.m] + userInfo [vendor/src/cocotron/CoreData/NSPropertyDescription.m] + windowNumbersWithOptions: [vendor/src/cocotron/AppKit/NSWindow.m] + +== SELECTORS THE APPLICATION USES THAT NOTHING HERE DEFINES: 486 (heuristic, includes the application's own) + absoluteString + accessibilityActionDescription: + accessibilityActionNames + accessibilityApplicationFocusedUIElement + accessibilityAttributeNames + accessibilityAttributeValue: + accessibilityAttributeValue:forParameter: + accessibilityAttributedStringForRange: + accessibilityChildren + accessibilityChildrenInNavigationOrder + accessibilityColumns + accessibilityContents + accessibilityFocusedUIElement + accessibilityFrame + accessibilityFrameForRange: + accessibilityHelp + accessibilityHitTest: + accessibilityHorizontalScrollBar + accessibilityIdentifier + accessibilityIsAttributeSettable: + accessibilityIsIgnored + accessibilityLabel + accessibilityLineForIndex: + accessibilityMaxValue + accessibilityMinValue + accessibilityNotifiesWhenDestroyed + accessibilityNumberOfCharacters + accessibilityOrientation + accessibilityParameterizedAttributeNames + accessibilityParent + accessibilityPerformAction: + accessibilityPerformDecrement + accessibilityPerformIncrement + accessibilityPerformPick + accessibilityPerformPress + accessibilityPerformShowMenu + accessibilityRTFForRange: + accessibilityRangeForIndex: + accessibilityRangeForLine: + accessibilityRangeForPosition: + accessibilityRole + accessibilityRoleDescription + accessibilityRows + accessibilitySelectedChildren + accessibilitySelectedText + accessibilitySelectedTextRange + accessibilityServesAsTitleForUIElements + accessibilitySetOverrideValue:forAttribute: + accessibilitySetValue:forAttribute: + accessibilitySharedCharacterRange + accessibilitySharedTextUIElements + accessibilityStringForRange: + accessibilityStyleRangeForIndex: + accessibilitySubrole + accessibilityTabs + accessibilityTitle + accessibilityTitleUIElement + accessibilityTopLevelUIElement + accessibilityValue + accessibilityVerticalScrollBar + ... and 426 more diff --git a/scripts/lo-work-list.py b/scripts/lo-work-list.py new file mode 100644 index 000000000..da0b63a0e --- /dev/null +++ b/scripts/lo-work-list.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""What LibreOffice needs that this fork has but does not MEAN, as one work list. + +The missing-symbol sweep (docs/libreoffice-gap.md) is a different question and is already +answered. It finds what the linker cannot resolve, and it is exhausted: the walls since then have +all been things that LINK CLEANLY AND DO NOTHING. + + a C function that exists and prints STUB and returns nil + an ObjC method that exists and calls NSUnimplementedMethod + a selector no class here implements at all + +None of those three is an undefined symbol, so none can be found by comparing symbol tables. This +finds them instead, by reading our own sources for the markers we already write, and intersecting +with what the application actually references. + +Usage: + scripts/lo-work-list.py ... +""" +import os +import re +import subprocess +import sys + +# A function body that contains one of these is present, linkable, and useless. +STUB_MARKERS = ("printf(\"STUB", "NSUnimplementedMethod(", "NSInvalidAbstractInvocation(") + +C_FUNC = re.compile( + r'^[A-Za-z_][A-Za-z0-9_ \t\*]*?\b([A-Za-z_][A-Za-z0-9_]*)\s*\([^;{]*\)\s*\{', re.M) +OBJC_METHOD = re.compile(r'^[-+]\s*\(([^)]*)\)\s*([^{;]+)\{', re.M) + + +def body_of(text, brace_pos): + """The text between the brace at brace_pos and its match, or a bounded slice.""" + depth, i, n = 0, brace_pos, len(text) + while i < n: + if text[i] == '{': + depth += 1 + elif text[i] == '}': + depth -= 1 + if depth == 0: + return text[brace_pos:i] + i += 1 + return text[brace_pos:brace_pos + 4000] + + +def selector_of(decl): + """The selector from an ObjC method declaration, e.g. 'setFoo: (int) x bar: (int) y'.""" + parts = re.findall(r'([A-Za-z_][A-Za-z0-9_]*:)', decl) + if parts: + return "".join(parts) + m = re.match(r'\s*([A-Za-z_][A-Za-z0-9_]*)\s*$', decl.strip()) + return m.group(1) if m else None + + +def scan_sources(roots): + """(stub_c_functions, stub_selectors, all_defined_selectors)""" + stub_funcs, stub_sels, all_sels = {}, {}, set() + for root in roots: + for dirpath, dirnames, filenames in os.walk(root): + dirnames[:] = [d for d in dirnames if d not in ("buck-out", ".git", ".jj", "submodules")] + for name in filenames: + if not name.endswith((".m", ".c", ".mm")): + continue + path = os.path.join(dirpath, name) + try: + text = open(path, errors="ignore").read() + except OSError: + continue + for m in OBJC_METHOD.finditer(text): + sel = selector_of(m.group(2)) + if not sel: + continue + all_sels.add(sel) + body = body_of(text, m.end() - 1) + if any(k in body for k in STUB_MARKERS): + stub_sels.setdefault(sel, path) + for m in C_FUNC.finditer(text): + fn = m.group(1) + if fn in ("if", "for", "while", "switch", "return", "sizeof"): + continue + body = body_of(text, m.end() - 1) + if any(k in body for k in STUB_MARKERS): + stub_funcs.setdefault(fn, path) + return stub_funcs, stub_sels, all_sels + + +def app_machos(bundle): + out = [] + for dirpath, _dirnames, filenames in os.walk(bundle): + for name in filenames: + p = os.path.join(dirpath, name) + if os.path.islink(p) or not os.path.isfile(p): + continue + try: + with open(p, "rb") as f: + if f.read(4) not in (b"\xcf\xfa\xed\xfe", b"\xce\xfa\xed\xfe"): + continue + except OSError: + continue + out.append(p) + return out + + +def app_imports_and_selectors(bundle): + imports, selectors = set(), set() + for path in app_machos(bundle): + try: + r = subprocess.run(["llvm-nm", "-u", path], capture_output=True, text=True, timeout=120) + for line in r.stdout.splitlines(): + s = line.strip() + if s.startswith("U "): + s = s[2:].strip() + if s.startswith("_"): + imports.add(s[1:]) + except Exception: + pass + # Selector names live in their own cstring section, which is exactly the set of + # selectors this binary can send. + try: + r = subprocess.run( + ["llvm-objdump", "--macho", "--section", "__TEXT,__objc_methname", path], + capture_output=True, text=True, timeout=180) + for line in r.stdout.splitlines(): + m = re.match(r'^[0-9a-f]+\s+(.+)$', line.strip()) + if m: + selectors.add(m.group(1).strip()) + except Exception: + pass + return imports, selectors + + +def main(): + if len(sys.argv) < 3: + raise SystemExit(__doc__) + bundle, roots = sys.argv[1], sys.argv[2:] + + print("reading the application...", file=sys.stderr) + imports, selectors = app_imports_and_selectors(bundle) + print(f" {len(imports)} imported symbols, {len(selectors)} selector names", file=sys.stderr) + + print("reading our sources...", file=sys.stderr) + stub_funcs, stub_sels, all_sels = scan_sources(roots) + print(f" {len(stub_funcs)} stubbed C functions, {len(stub_sels)} stubbed methods, " + f"{len(all_sels)} methods defined", file=sys.stderr) + + wanted_funcs = sorted(f for f in stub_funcs if f in imports) + wanted_sels = sorted(s for s in stub_sels if s in selectors) + # A selector the application can send that NOTHING here defines. Heuristic by nature: the + # application also sends selectors to its own classes, so this over-reports and is ranked + # last for that reason. + absent = sorted(s for s in selectors + if s not in all_sels and re.match(r'^[a-z][A-Za-z0-9_:]*$', s) + and (s.startswith("accessibility") or s.startswith("set") or ":" not in s)) + + print(f"\n== STUBBED C FUNCTIONS THE APPLICATION IMPORTS: {len(wanted_funcs)}") + for f in wanted_funcs: + print(f" {f} [{stub_funcs[f]}]") + + print(f"\n== STUBBED METHODS WHOSE SELECTOR THE APPLICATION USES: {len(wanted_sels)}") + for s in wanted_sels: + print(f" {s} [{stub_sels[s]}]") + + print(f"\n== SELECTORS THE APPLICATION USES THAT NOTHING HERE DEFINES: {len(absent)}" + f" (heuristic, includes the application's own)") + for s in absent[:60]: + print(f" {s}") + if len(absent) > 60: + print(f" ... and {len(absent) - 60} more") + + +if __name__ == "__main__": + main() diff --git a/vendor/patches/cocotron/0015-colorspace-named-spaces.patch b/vendor/patches/cocotron/0015-colorspace-named-spaces.patch new file mode 100644 index 000000000..612a9ae2f --- /dev/null +++ b/vendor/patches/cocotron/0015-colorspace-named-spaces.patch @@ -0,0 +1,44 @@ +diff -u --recursive a/Onyx2D/O2ColorSpace.m b/Onyx2D/O2ColorSpace.m +--- a/Onyx2D/O2ColorSpace.m 2026-08-13 22:12:11.800560362 +0200 ++++ b/Onyx2D/O2ColorSpace.m 2026-08-13 22:12:11.814382827 +0200 +@@ -133,7 +133,6 @@ + } + + O2ColorSpaceRef O2ColorSpaceCreateWithName(CFStringRef name) { +- printf("CALLED: O2ColorSpaceCreateWithName\n"); + if (CFStringCompare(name, kO2ColorSpaceSRGB, 0) == kCFCompareEqualTo) { + O2ColorSpaceRef cs = [[O2ColorSpace alloc] initWithDeviceRGB]; + cs->_name = kO2ColorSpaceSRGB; +@@ -144,6 +143,32 @@ + cs->_type = kO2ColorSpaceModelRGB; + return cs; + } ++ /* ++ * THE REST OF THE NAMED SPACES. Only sRGB and Display P3 were handled, and everything else ++ * returned NULL, which is not a degraded colour: a caller that asks for a colour space and ++ * gets nothing cannot make a context, and the failure surfaces somewhere unrelated. ++ * LibreOffice asks for GenericGrayGamma2_2 while bringing its GUI up and died on it, with ++ * "unknown color space name" being the only clue and appearing two lines earlier. ++ * ++ * THE GAMMA IS NOT MODELLED, and that is the honest limitation to write down rather than ++ * hide: this maps each name to the device space with the right NUMBER OF COMPONENTS, so a ++ * grey stays grey and an RGB stays RGB, but a 2.2 gamma grey and a linear grey come out the ++ * same. That is a colour accuracy difference, visible only in a side by side comparison, ++ * where the alternative was a process that does not start. ++ */ ++ if (CFStringCompare(name, kO2ColorSpaceGenericGray, 0) == kCFCompareEqualTo || ++ CFStringCompare(name, kO2ColorSpaceGenericGrayGamma2_2, 0) == kCFCompareEqualTo) { ++ return [[O2ColorSpace alloc] initWithDeviceGray]; ++ } ++ if (CFStringCompare(name, kO2ColorSpaceGenericRGB, 0) == kCFCompareEqualTo || ++ CFStringCompare(name, kO2ColorSpaceGenericRGBLinear, 0) == kCFCompareEqualTo || ++ CFStringCompare(name, kO2ColorSpaceAdobeRGB1998, 0) == kCFCompareEqualTo) { ++ return [[O2ColorSpace alloc] initWithDeviceRGB]; ++ } ++ if (CFStringCompare(name, kO2ColorSpaceGenericCMYK, 0) == kCFCompareEqualTo) { ++ return [[O2ColorSpace alloc] initWithDeviceCMYK]; ++ } ++ + printf("unknown color space name\n"); + CFShow(name); + return NULL;