mirror of
https://github.com/rizsotto/Bear.git
synced 2026-05-28 00:20:45 +02:00
fix(msvc): handle all per-warning cl.exe options
cl.exe accepts a warning-number argument either glued (/wd4995) or as a separate token (/wd 4995); the latter form is widely used in nmake Makefiles. Three gaps on the current 4.1.2-rc tip: 1. `/wd*`, `/we*`, `/wo*` used a plain prefix pattern that matches the glued form only. When bear saw "/wd 4995", the flag consumed zero extra args and the trailing numeric token was reclassified as a Source and dropped from compile_commands.json. clangd then emitted drv_invalid_int_value for every translation unit. 2. `/w1nnnn`, `/w2nnnn`, `/w3nnnn`, `/w4nnnn` (set warning level for a specific warning) were not defined at all, so `/w1 4326` was split into an unknown flag plus an orphan numeric token. 3. `/Wv[:version]` was not defined. Both the bare `/Wv` form (cl uses the current compiler version when omitted) and `/Wv:17` were affected. All three classes are documented on the MS warning-level options page: https://learn.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level Fix: * /wd*, /we*, /wo* -> /wd{ }*, /we{ }*, /wo{ }* (ExactlyWithGluedOrSep, matching /D, /I, /U, /FI). * Add /w1{ }*, /w2{ }*, /w3{ }*, /w4{ }*. * Add /Wv (exact) plus /Wv:* (ExactlyWithColon, required value). clang_cl.yaml inherits the fix via `extends: msvc`. Codegen snapshot fixtures updated accordingly. Two integration tests in integration-tests/tests/cases/semantic.rs cover all three classes. Manually verified by scc-tw <scc@scc.tw>. Closes: #690 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
---
|
||||
source: bear-codegen/tests/snapshots.rs
|
||||
assertion_line: 32
|
||||
expression: "generate_flag_file(\"clang_cl\")"
|
||||
---
|
||||
// Generated from interpreters/clang_cl.yaml -- DO NOT EDIT
|
||||
static CLANG_CL_FLAGS: [FlagRule; 163] = [
|
||||
static CLANG_CL_FLAGS: [FlagRule; 169] = [
|
||||
FlagRule::new(FlagPattern::ExactlyWithEqOrSep("-fms-compatibility-version"), ArgumentKind::Other(PassEffect::Configures(CompilerPass::Compiling))),
|
||||
FlagRule::new(FlagPattern::Exactly("-fprofile-instr-generate", 0), ArgumentKind::Other(PassEffect::Configures(CompilerPass::Compiling))),
|
||||
FlagRule::new(FlagPattern::ExactlyWithEq("-fprofile-instr-generate"), ArgumentKind::Other(PassEffect::Configures(CompilerPass::Compiling))),
|
||||
@@ -139,9 +140,15 @@ static CLANG_CL_FLAGS: [FlagRule; 163] = [
|
||||
FlagRule::new(FlagPattern::Exactly("/W2", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Exactly("/W3", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Exactly("/W4", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Prefix("/wd", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Prefix("/we", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Prefix("/wo", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithColon("/Wv"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Exactly("/Wv", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/w1"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/w2"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/w3"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/w4"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/wd"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/we"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/wo"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Exactly("/FC", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithColonOrSep("/Yc"), ArgumentKind::Other(PassEffect::Configures(CompilerPass::Compiling))),
|
||||
FlagRule::new(FlagPattern::ExactlyWithColonOrSep("/Yu"), ArgumentKind::Other(PassEffect::Configures(CompilerPass::Compiling))),
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
source: bear-codegen/tests/snapshots.rs
|
||||
assertion_line: 62
|
||||
expression: "generate_flag_file(\"msvc\")"
|
||||
---
|
||||
// Generated from interpreters/msvc.yaml -- DO NOT EDIT
|
||||
static MSVC_FLAGS: [FlagRule; 127] = [
|
||||
static MSVC_FLAGS: [FlagRule; 133] = [
|
||||
FlagRule::new(FlagPattern::Exactly("/external:anglebrackets", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Exactly("/Qfast_transcendentals", 0), ArgumentKind::Other(PassEffect::Configures(CompilerPass::Compiling))),
|
||||
FlagRule::new(FlagPattern::ExactlyWithColon("/execution-charset"), ArgumentKind::Other(PassEffect::Configures(CompilerPass::Compiling))),
|
||||
@@ -104,9 +105,15 @@ static MSVC_FLAGS: [FlagRule; 127] = [
|
||||
FlagRule::new(FlagPattern::Exactly("/W2", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Exactly("/W3", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Exactly("/W4", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Prefix("/wd", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Prefix("/we", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Prefix("/wo", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithColon("/Wv"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Exactly("/Wv", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/w1"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/w2"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/w3"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/w4"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/wd"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/we"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithGluedOrSep("/wo"), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::Exactly("/FC", 0), ArgumentKind::Other(PassEffect::None)),
|
||||
FlagRule::new(FlagPattern::ExactlyWithColonOrSep("/Yc"), ArgumentKind::Other(PassEffect::Configures(CompilerPass::Compiling))),
|
||||
FlagRule::new(FlagPattern::ExactlyWithColonOrSep("/Yu"), ArgumentKind::Other(PassEffect::Configures(CompilerPass::Compiling))),
|
||||
|
||||
@@ -220,11 +220,28 @@ flags:
|
||||
result: none
|
||||
- match: {pattern: "/w"}
|
||||
result: none
|
||||
- match: {pattern: "/wd*"}
|
||||
# /Wv[:version]: the value is optional (cl uses the current compiler version
|
||||
# when omitted), so we accept both the bare form and "/Wv:<version>".
|
||||
- match: {pattern: "/Wv:*"}
|
||||
result: none
|
||||
- match: {pattern: "/we*"}
|
||||
- match: {pattern: "/Wv"}
|
||||
result: none
|
||||
- match: {pattern: "/wo*"}
|
||||
# cl.exe accepts the warning-number value either glued ("/wd4995") or as a
|
||||
# separate argument ("/wd 4995") for all per-warning options; the patterns
|
||||
# below must consume both forms.
|
||||
- match: {pattern: "/w1{ }*"}
|
||||
result: none
|
||||
- match: {pattern: "/w2{ }*"}
|
||||
result: none
|
||||
- match: {pattern: "/w3{ }*"}
|
||||
result: none
|
||||
- match: {pattern: "/w4{ }*"}
|
||||
result: none
|
||||
- match: {pattern: "/wd{ }*"}
|
||||
result: none
|
||||
- match: {pattern: "/we{ }*"}
|
||||
result: none
|
||||
- match: {pattern: "/wo{ }*"}
|
||||
result: none
|
||||
- match: {pattern: "/diagnostics:*"}
|
||||
result: none
|
||||
|
||||
@@ -504,3 +504,139 @@ fn semantic_output_format() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test: all MSVC per-warning options documented on
|
||||
/// <https://learn.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level>
|
||||
/// accept their numeric value either glued (`/wd4995`) or separated by whitespace
|
||||
/// (`/wd 4995`). Both forms are emitted by real `cl.exe` invocations and by
|
||||
/// Makefiles in the wild (e.g. `CFLAGS = /wd 4995 /wd 4996 ...`). The separated
|
||||
/// form must survive semantic analysis intact; dropping the number silently would
|
||||
/// corrupt compile_commands.json and break downstream tools such as clangd
|
||||
/// (emits `drv_invalid_int_value` per translation unit).
|
||||
///
|
||||
/// Covers `/w1`, `/w2`, `/w3`, `/w4` (set warning level for a specific warning)
|
||||
/// and `/wd`, `/we`, `/wo` (disable / as-error / report-once).
|
||||
///
|
||||
/// This test is platform-independent: it exercises the `semantic` subcommand on
|
||||
/// a hand-crafted events file and does not require a real `cl.exe` to be present.
|
||||
#[test]
|
||||
fn msvc_per_warning_options_preserve_separated_value() -> Result<()> {
|
||||
let env = TestEnvironment::new("msvc_per_warning_options_separated")?;
|
||||
let temp_dir = env.test_dir().to_str().unwrap();
|
||||
|
||||
// Use a bare "cl.exe" -- the recognizer matches on the filename stem only, so we
|
||||
// do not need the file to exist on disk. Keeps the test hermetic across platforms.
|
||||
let cl = "cl.exe";
|
||||
|
||||
let event = json!({
|
||||
"pid": 1,
|
||||
"execution": {
|
||||
"executable": cl,
|
||||
"arguments": [
|
||||
cl,
|
||||
"/w1", "4100",
|
||||
"/w2", "4101",
|
||||
"/w3", "4102",
|
||||
"/w4", "4103",
|
||||
"/wd", "4995",
|
||||
"/we", "4996",
|
||||
"/wo", "4819",
|
||||
"/c", "test.c",
|
||||
],
|
||||
"working_dir": temp_dir,
|
||||
"environment": {}
|
||||
}
|
||||
});
|
||||
|
||||
env.create_source_files(&[
|
||||
("events.json", &event.to_string()),
|
||||
("test.c", "int main(void) { return 0; }"),
|
||||
])?;
|
||||
|
||||
env.run_bear_success(&["semantic", "--input", "events.json", "--output", "compile_commands.json"])?;
|
||||
|
||||
let db = env.load_compilation_database("compile_commands.json")?;
|
||||
db.assert_count(1)?;
|
||||
|
||||
// Each flag/value pair must round-trip with its numeric value intact. Before
|
||||
// the fix, these flags matched a prefix-only pattern, so the standalone
|
||||
// numeric token following each flag was reclassified as a source file and
|
||||
// dropped from the output.
|
||||
db.assert_contains(&compilation_entry!(
|
||||
file: "test.c".to_string(),
|
||||
directory: temp_dir.to_string(),
|
||||
arguments: vec![
|
||||
cl.to_string(),
|
||||
"/w1".to_string(), "4100".to_string(),
|
||||
"/w2".to_string(), "4101".to_string(),
|
||||
"/w3".to_string(), "4102".to_string(),
|
||||
"/w4".to_string(), "4103".to_string(),
|
||||
"/wd".to_string(), "4995".to_string(),
|
||||
"/we".to_string(), "4996".to_string(),
|
||||
"/wo".to_string(), "4819".to_string(),
|
||||
"/c".to_string(),
|
||||
"test.c".to_string(),
|
||||
]
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test: `/Wv[:version]` has an optional value (cl uses the current
|
||||
/// compiler version when omitted). Both forms -- bare `/Wv` and `/Wv:17` -- must
|
||||
/// round-trip through semantic analysis without losing tokens or dropping the
|
||||
/// entry.
|
||||
#[test]
|
||||
fn msvc_wv_optional_version_is_preserved() -> Result<()> {
|
||||
let env = TestEnvironment::new("msvc_wv_optional_version")?;
|
||||
let temp_dir = env.test_dir().to_str().unwrap();
|
||||
|
||||
let cl = "cl.exe";
|
||||
|
||||
// Two translation units, one per /Wv form, so the test exercises both paths
|
||||
// in a single run.
|
||||
let event_bare = json!({
|
||||
"pid": 1,
|
||||
"execution": {
|
||||
"executable": cl,
|
||||
"arguments": [cl, "/Wv", "/c", "bare.c"],
|
||||
"working_dir": temp_dir,
|
||||
"environment": {}
|
||||
}
|
||||
});
|
||||
let event_with_version = json!({
|
||||
"pid": 2,
|
||||
"execution": {
|
||||
"executable": cl,
|
||||
"arguments": [cl, "/Wv:17", "/c", "versioned.c"],
|
||||
"working_dir": temp_dir,
|
||||
"environment": {}
|
||||
}
|
||||
});
|
||||
|
||||
let events = format!("{}\n{}", event_bare, event_with_version);
|
||||
|
||||
env.create_source_files(&[
|
||||
("events.json", &events),
|
||||
("bare.c", "int main(void) { return 0; }"),
|
||||
("versioned.c", "int main(void) { return 0; }"),
|
||||
])?;
|
||||
|
||||
env.run_bear_success(&["semantic", "--input", "events.json", "--output", "compile_commands.json"])?;
|
||||
|
||||
let db = env.load_compilation_database("compile_commands.json")?;
|
||||
db.assert_count(2)?;
|
||||
|
||||
db.assert_contains(&compilation_entry!(
|
||||
file: "bare.c".to_string(),
|
||||
directory: temp_dir.to_string(),
|
||||
arguments: vec![cl.to_string(), "/Wv".to_string(), "/c".to_string(), "bare.c".to_string()]
|
||||
))?;
|
||||
db.assert_contains(&compilation_entry!(
|
||||
file: "versioned.c".to_string(),
|
||||
directory: temp_dir.to_string(),
|
||||
arguments: vec![cl.to_string(), "/Wv:17".to_string(), "/c".to_string(), "versioned.c".to_string()]
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user