fortrangoingonforty/afs-ld / cab9755

Browse files

Complete undefined mode wiring

Authored by espadonne
SHA
cab975558b8b2e000f027bc663f71623dc6ff949
Parents
28ec20f
Tree
9a4e950

6 changed files

StatusFile+-
M src/args.rs 16 13
M src/diag.rs 6 0
M src/lib.rs 11 2
M src/main.rs 1 1
M src/resolve.rs 45 16
M tests/cli_diagnostics.rs 124 0
src/args.rsmodified
@@ -239,21 +239,15 @@ pub fn parse(argv: &[String]) -> Result<LinkOptions, ArgsError> {
239239
                     .ok_or_else(|| ArgsError::MissingValue("-undefined".into()))?;
240240
                 opts.undefined_treatment = match value.as_str() {
241241
                     "error" => UndefinedTreatment::Error,
242
+                    "warning" => UndefinedTreatment::Warning,
243
+                    "suppress" => UndefinedTreatment::Suppress,
242244
                     "dynamic_lookup" => UndefinedTreatment::DynamicLookup,
243
-                    "warning" | "suppress" => {
244
-                        return Err(ArgsError::InvalidValue {
245
-                            flag: "-undefined".into(),
246
-                            value: value.clone(),
247
-                            expected:
248
-                                "`error` or `dynamic_lookup` (warning/suppress not yet supported)"
249
-                                    .into(),
250
-                        });
251
-                    }
252245
                     _ => {
253246
                         return Err(ArgsError::InvalidValue {
254247
                             flag: "-undefined".into(),
255248
                             value: value.clone(),
256
-                            expected: "`error` or `dynamic_lookup`".into(),
249
+                            expected: "`error`, `warning`, `suppress`, or `dynamic_lookup`"
250
+                                .into(),
257251
                         });
258252
                     }
259253
                 };
@@ -601,15 +595,24 @@ mod tests {
601595
     }
602596
 
603597
     #[test]
604
-    fn undefined_flag_rejects_unsupported_modes() {
605
-        let err = parse(&argv(&["-undefined", "warning", "main.o"])).unwrap_err();
598
+    fn undefined_flag_records_warning_and_suppress() {
599
+        let warning = parse(&argv(&["-undefined", "warning", "main.o"])).unwrap();
600
+        assert_eq!(warning.undefined_treatment, UndefinedTreatment::Warning);
601
+
602
+        let suppress = parse(&argv(&["-undefined", "suppress", "main.o"])).unwrap();
603
+        assert_eq!(suppress.undefined_treatment, UndefinedTreatment::Suppress);
604
+    }
605
+
606
+    #[test]
607
+    fn undefined_flag_rejects_unknown_modes() {
608
+        let err = parse(&argv(&["-undefined", "bogus", "main.o"])).unwrap_err();
606609
         assert!(matches!(
607610
             err,
608611
             ArgsError::InvalidValue {
609612
                 ref flag,
610613
                 ref value,
611614
                 ..
612
-            } if flag == "-undefined" && value == "warning"
615
+            } if flag == "-undefined" && value == "bogus"
613616
         ));
614617
     }
615618
 
src/diag.rsmodified
@@ -22,3 +22,9 @@ pub fn warning(msg: &str) {
2222
     let mut h = stderr.lock();
2323
     let _ = writeln!(h, "afs-ld: warning: {msg}");
2424
 }
25
+
26
+pub fn warning_verbatim(msg: &str) {
27
+    let stderr = std::io::stderr();
28
+    let mut h = stderr.lock();
29
+    let _ = writeln!(h, "{msg}");
30
+}
src/lib.rsmodified
@@ -34,8 +34,9 @@ use macho::tbd::{parse_tbd, parse_version, Arch, Platform, Target};
3434
 use reloc::arm64::RelocError;
3535
 use resolve::{
3636
     classify_unresolved, drain_fetches, find_archive_by_path, force_load_all, force_load_archive,
37
-    format_duplicate_diagnostic, format_undefined_diagnostic, seed_all, DrainReport, DylibLoadMeta,
38
-    InputAddError, Inputs, Symbol, SymbolTable, UndefinedTreatment,
37
+    format_duplicate_diagnostic, format_undefined_diagnostic,
38
+    format_undefined_warning_diagnostic, seed_all, DrainReport, DylibLoadMeta, InputAddError,
39
+    Inputs, Symbol, SymbolTable, UndefinedTreatment,
3940
 };
4041
 
4142
 const DEFAULT_TBD_VERSION: u32 = 1 << 16;
@@ -424,6 +425,14 @@ impl Linker {
424425
                 &unresolved.errors,
425426
             )));
426427
         }
428
+        if !unresolved.warnings.is_empty() {
429
+            crate::diag::warning_verbatim(&format_undefined_warning_diagnostic(
430
+                &sym_table,
431
+                &inputs,
432
+                &referrers,
433
+                &unresolved.warnings,
434
+            ));
435
+        }
427436
 
428437
         let mut atom_table = AtomTable::new();
429438
         let mut objects = Vec::new();
src/main.rsmodified
@@ -19,7 +19,7 @@ Options:
1919
                                   Set LC_BUILD_VERSION payload
2020
   -r                              Relocatable output (deferred; errors)
2121
   -bundle                         Bundle output (deferred; errors)
22
-  -undefined <error|dynamic_lookup>
22
+  -undefined <error|warning|suppress|dynamic_lookup>
2323
                                   Control unresolved-symbol treatment
2424
   -rpath <path>                   Add LC_RPATH
2525
   -install_name <path>            Override dylib install name
src/resolve.rsmodified
@@ -1296,9 +1296,11 @@ impl DylibId {
12961296
 pub struct ClassificationReport {
12971297
     /// Strong undefineds that triggered errors under `Error` treatment.
12981298
     pub errors: Vec<Unresolved>,
1299
-    /// Strong undefineds that produced warnings under `Warning` treatment.
1299
+    /// Strong undefineds that produced warnings under `Warning` treatment and
1300
+    /// were promoted to flat-lookup imports for final emission.
13001301
     pub warnings: Vec<Unresolved>,
1301
-    /// Strong undefineds that were silently accepted under `Suppress`.
1302
+    /// Strong undefineds that were silently accepted under `Suppress` and
1303
+    /// were promoted to flat-lookup imports for final emission.
13021304
     pub suppressed: Vec<Unresolved>,
13031305
     /// Undefineds promoted to flat-lookup DylibImport entries.
13041306
     pub promoted_to_dynamic: Vec<SymbolId>,
@@ -1372,16 +1374,17 @@ pub fn did_you_mean(table: &SymbolTable, query: &str, budget: usize, max: usize)
13721374
 /// Format the full undefined-symbol diagnostic block, one entry per
13731375
 /// unresolved name: the error line, every referrer, and an optional
13741376
 /// did-you-mean hint.
1375
-pub fn format_undefined_diagnostic(
1377
+fn format_undefined_diagnostic_with_level(
13761378
     table: &SymbolTable,
13771379
     inputs: &Inputs,
13781380
     referrers: &ReferrerLog,
13791381
     unresolved: &[Unresolved],
1382
+    level: &str,
13801383
 ) -> String {
13811384
     let mut out = String::new();
13821385
     for u in unresolved {
13831386
         let name = table.interner.resolve(u.name);
1384
-        out.push_str(&format!("afs-ld: error: undefined symbol: {name}\n"));
1387
+        out.push_str(&format!("afs-ld: {level}: undefined symbol: {name}\n"));
13851388
         for origin in referrers.get(u.name) {
13861389
             if let Some(oi) = inputs.objects.get(origin.0 as usize) {
13871390
                 out.push_str(&format!("      referenced by {}\n", oi.path.display()));
@@ -1402,6 +1405,24 @@ pub fn format_undefined_diagnostic(
14021405
     out
14031406
 }
14041407
 
1408
+pub fn format_undefined_diagnostic(
1409
+    table: &SymbolTable,
1410
+    inputs: &Inputs,
1411
+    referrers: &ReferrerLog,
1412
+    unresolved: &[Unresolved],
1413
+) -> String {
1414
+    format_undefined_diagnostic_with_level(table, inputs, referrers, unresolved, "error")
1415
+}
1416
+
1417
+pub fn format_undefined_warning_diagnostic(
1418
+    table: &SymbolTable,
1419
+    inputs: &Inputs,
1420
+    referrers: &ReferrerLog,
1421
+    unresolved: &[Unresolved],
1422
+) -> String {
1423
+    format_undefined_diagnostic_with_level(table, inputs, referrers, unresolved, "warning")
1424
+}
1425
+
14051426
 /// Format a `DuplicateStrong` insertion error for user consumption. Needs
14061427
 /// the incumbent symbol (from the table) plus the losing second symbol
14071428
 /// carried in the error itself.
@@ -1442,6 +1463,21 @@ pub fn classify_unresolved(
14421463
 ) -> ClassificationReport {
14431464
     let mut report = ClassificationReport::default();
14441465
 
1466
+    fn promote_to_flat_lookup(table: &mut SymbolTable, id: SymbolId, name: Istr) {
1467
+        table.symbols[id.0 as usize] = Symbol::DylibImport {
1468
+            name,
1469
+            dylib: DylibId::INVALID,
1470
+            ordinal: FLAT_LOOKUP_ORDINAL,
1471
+            weak_import: true,
1472
+        };
1473
+        table.transitions.push(Transition {
1474
+            id,
1475
+            from: SymbolKindTag::Undefined,
1476
+            to: SymbolKindTag::DylibImport,
1477
+            cause: TransitionCause::Replaced,
1478
+        });
1479
+    }
1480
+
14451481
     // Collect undefineds before mutating — avoids double-borrow grief.
14461482
     let undefs: Vec<(SymbolId, Istr, bool)> = table
14471483
         .iter()
@@ -1462,23 +1498,16 @@ pub fn classify_unresolved(
14621498
             }
14631499
             UndefinedTreatment::Warning => {
14641500
                 report.warnings.push(Unresolved { name, id });
1501
+                promote_to_flat_lookup(table, id, name);
1502
+                report.promoted_to_dynamic.push(id);
14651503
             }
14661504
             UndefinedTreatment::Suppress => {
14671505
                 report.suppressed.push(Unresolved { name, id });
1506
+                promote_to_flat_lookup(table, id, name);
1507
+                report.promoted_to_dynamic.push(id);
14681508
             }
14691509
             UndefinedTreatment::DynamicLookup => {
1470
-                table.symbols[id.0 as usize] = Symbol::DylibImport {
1471
-                    name,
1472
-                    dylib: DylibId::INVALID,
1473
-                    ordinal: FLAT_LOOKUP_ORDINAL,
1474
-                    weak_import: true,
1475
-                };
1476
-                table.transitions.push(Transition {
1477
-                    id,
1478
-                    from: SymbolKindTag::Undefined,
1479
-                    to: SymbolKindTag::DylibImport,
1480
-                    cause: TransitionCause::Replaced,
1481
-                });
1510
+                promote_to_flat_lookup(table, id, name);
14821511
                 report.promoted_to_dynamic.push(id);
14831512
             }
14841513
         }
tests/cli_diagnostics.rsmodified
@@ -14,6 +14,17 @@ fn have_xcrun() -> bool {
1414
         .unwrap_or(false)
1515
 }
1616
 
17
+fn sdk_path() -> Option<String> {
18
+    let out = Command::new("xcrun")
19
+        .args(["--sdk", "macosx", "--show-sdk-path"])
20
+        .output()
21
+        .ok()?;
22
+    if !out.status.success() {
23
+        return None;
24
+    }
25
+    Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
26
+}
27
+
1728
 fn minimal_main_src() -> &'static str {
1829
     r#"
1930
         .section __TEXT,__text,regular,pure_instructions
@@ -120,6 +131,7 @@ fn help_flag_prints_usage_and_exits_successfully() {
120131
     assert!(stdout.contains("-map <path>"));
121132
     assert!(stdout.contains("-no_uuid"));
122133
     assert!(stdout.contains("-dead_strip"));
134
+    assert!(stdout.contains("-undefined <error|warning|suppress|dynamic_lookup>"));
123135
     assert!(stdout.contains("-t, -trace"));
124136
     assert!(stdout.contains("-v, --version"));
125137
 }
@@ -299,6 +311,118 @@ fn undefined_symbol_diagnostic_is_not_double_prefixed() {
299311
     let _ = fs::remove_file(obj);
300312
 }
301313
 
314
+#[test]
315
+fn undefined_warning_mode_links_and_warns_once() {
316
+    if !have_xcrun() {
317
+        eprintln!("skipping: xcrun as unavailable");
318
+        return;
319
+    }
320
+    let Some(sdk) = sdk_path() else {
321
+        eprintln!("skipping: xcrun --show-sdk-path unavailable");
322
+        return;
323
+    };
324
+
325
+    let exe = env!("CARGO_BIN_EXE_afs-ld");
326
+    let obj = scratch("missing-warning.o");
327
+    let src = r#"
328
+        .section __TEXT,__text,regular,pure_instructions
329
+        .globl _main
330
+        _main:
331
+            bl _missing
332
+            ret
333
+        .subsections_via_symbols
334
+    "#;
335
+    if let Err(e) = assemble(src, &obj) {
336
+        eprintln!("skipping: assemble failed: {e}");
337
+        return;
338
+    }
339
+
340
+    let out_path = scratch("missing-warning.out");
341
+    let out = Command::new(exe)
342
+        .arg("-undefined")
343
+        .arg("warning")
344
+        .arg("-syslibroot")
345
+        .arg(&sdk)
346
+        .arg("-lSystem")
347
+        .arg("-o")
348
+        .arg(&out_path)
349
+        .arg(&obj)
350
+        .output()
351
+        .expect("afs-ld should run");
352
+    assert!(
353
+        out.status.success(),
354
+        "-undefined warning should link successfully:\nstderr:\n{}",
355
+        String::from_utf8_lossy(&out.stderr)
356
+    );
357
+    let stderr = String::from_utf8_lossy(&out.stderr);
358
+    assert!(
359
+        stderr.contains("afs-ld: warning: undefined symbol: _missing"),
360
+        "missing expected undefined-symbol warning:\n{stderr}"
361
+    );
362
+    assert!(
363
+        !stderr.contains("afs-ld: warning: afs-ld: warning:"),
364
+        "warning diagnostic was double-prefixed:\n{stderr}"
365
+    );
366
+    assert!(out_path.exists(), "expected linked output to be written");
367
+
368
+    let _ = fs::remove_file(obj);
369
+    let _ = fs::remove_file(out_path);
370
+}
371
+
372
+#[test]
373
+fn undefined_suppress_mode_links_silently() {
374
+    if !have_xcrun() {
375
+        eprintln!("skipping: xcrun as unavailable");
376
+        return;
377
+    }
378
+    let Some(sdk) = sdk_path() else {
379
+        eprintln!("skipping: xcrun --show-sdk-path unavailable");
380
+        return;
381
+    };
382
+
383
+    let exe = env!("CARGO_BIN_EXE_afs-ld");
384
+    let obj = scratch("missing-suppress.o");
385
+    let src = r#"
386
+        .section __TEXT,__text,regular,pure_instructions
387
+        .globl _main
388
+        _main:
389
+            bl _missing
390
+            ret
391
+        .subsections_via_symbols
392
+    "#;
393
+    if let Err(e) = assemble(src, &obj) {
394
+        eprintln!("skipping: assemble failed: {e}");
395
+        return;
396
+    }
397
+
398
+    let out_path = scratch("missing-suppress.out");
399
+    let out = Command::new(exe)
400
+        .arg("-undefined")
401
+        .arg("suppress")
402
+        .arg("-syslibroot")
403
+        .arg(&sdk)
404
+        .arg("-lSystem")
405
+        .arg("-o")
406
+        .arg(&out_path)
407
+        .arg(&obj)
408
+        .output()
409
+        .expect("afs-ld should run");
410
+    assert!(
411
+        out.status.success(),
412
+        "-undefined suppress should link successfully:\nstderr:\n{}",
413
+        String::from_utf8_lossy(&out.stderr)
414
+    );
415
+    let stderr = String::from_utf8_lossy(&out.stderr);
416
+    assert!(
417
+        !stderr.contains("undefined symbol: _missing"),
418
+        "expected -undefined suppress to omit undefined diagnostic:\n{stderr}"
419
+    );
420
+    assert!(out_path.exists(), "expected linked output to be written");
421
+
422
+    let _ = fs::remove_file(obj);
423
+    let _ = fs::remove_file(out_path);
424
+}
425
+
302426
 #[test]
303427
 fn trace_flag_prints_loaded_inputs_and_archive_members() {
304428
     if !have_xcrun() {