C++ 隱含轉換

目標和動力

C/C++ 允許在型別之間轉換值,且可能以修改 可能因為溢位、反向溢位、符號變化等原因而造成 以及精確度下降等因素

這類轉換可能間接進行,若未指定 如果作者希望有轉換發生,或是明確指定轉換發生 表示轉換作業屬實,且作者認得 或非預期的結果

根據預設,Fchsia C/C++ 程式碼會使用 旗標編譯 -Wconversion 禁止隱式類型轉換 可以變更值。這個新的預設行為在 2020 年推出時,來源 先前發生錯誤的程式碼維持不變,並保留相關版本 定義遭到抑制我們要清理這個遺產,因為 如略過這項警告,可能會隱藏程式碼中真正的細微錯誤。

技術背景

可能會改變值的隱含類型轉換範例:

float f = sqrt(2.0);
int   si = 0.5;
unsigned int ui = -1;

除非系統抑制警告,否則這些錯誤會產生編譯器錯誤。

不會改變值的隱含類型轉換範例:

double d = sqrtf(2.0f);
size_t size = 20;
int i = 4.0;

這些指令將由編譯器判定為安全,絕對不會發出警示 隱含類型轉換類型

透過隱含類型轉換編譯程式碼的 BUILD.gn 定義 隱藏的警告如下:

source_set("foo") {
  ...
  # TODO(https://fxbug.dev/42136089): delete the below and fix compiler warnings
  configs += [ "//build/config:Wno-conversion" ]
}

//build/config/BUILD.gn 中 (成效:visibility) "Wno-conversion" 設定目標的清單會列出 來源樹狀結構中有這種抑制情況的執行個體。

如何提供協助

選取工作

選擇 Wno-conversionbug 42136089 所在的任何例項 參照。選擇性瀏覽 //build/config/BUILD.gn 用於參照任何程式碼 您熟悉的 Google Cloud 服務

您可以忽略第三方程式碼 (目錄路徑中的 third_party)。 Fuchsia 專案不是此程式碼的擁有者,因此您無法修正。 如有需要,可以前往上游程式碼位置貢獻一己之力 有什麼解決方法也就是說,上游維護人員也必須同意 以便啟用 -Wconversion 旗標,否則會迴歸。

執行工作

移除 -Wno-conversion 隱藏、重新建構並修正所有錯誤。

請注意,可能不會顯示任何錯誤。通常發生這種情況 其他人變更了原本屬於隱式類型的原始碼 轉換問題,但並未移除略過項目。如果沒有 修正後,即可繼續將變更送審。否則,請繼續閱讀。

大量程式碼完全可能是無謂 遭到隱藏。如果您移除了多次抑制,且變更通過 CQ 一點都不出錯

範例:

簡易停機時間

[5838/95510] CC
obj/src/graphics/lib/compute/spinel/ext/geometry/geometry.svg_arc.c.o
../../src/graphics/lib/compute/spinel/ext/geometry/svg_arc.c:96:57: error:
implicit conversion loses floating-point precision: 'double' to 'float'
[-Werror,-Wimplicit-float-conversion]
  arc_params->phi = fmodf(x_axis_rotation_radians, M_PI * 2.0);
                      ~~~~~                          ~~~~~^~~~~

在這種情況下,您只需重新編寫違規的程式碼, 明確轉換

arc_params->phi = fmodf(x_axis_rotation_radians, (float)(M_PI * 2.0));

這可以沒問題,因為將雙精度浮點數轉換為浮點值並不是問題 這個程式碼但其他變更較為複雜。舉例來說 先加入邊界檢查,再執行轉換。

變更變數類型

因此 來源變數類型,而非套用明確轉換。

../../garnet/bin/hwstress/memory_stress.cc:216:90: error: implicit conversion changes signedness: 'int' to 'uint64_t' (aka 'unsigned long') [-Werror,-Wsign-conversion]
                            MultiWordPattern(NegateWords((RotatePattern(every_sixth_bit, i))))));
                                                          ~~~~~~~~~~~~~                  ^

以下說明更多程式碼:

std::vector<uint64_t> RotatePattern(std::vector<uint64_t> v, uint64_t n);
  ...
  for (int i = 0; i < 6; i++) {
    result.push_back(MakePatternWorkload(
        fxl::StringPrintf("Single bit clear 6-bit (%d/6)", i + 1),
        MultiWordPattern(NegateWords((RotatePattern(every_sixth_bit, i))))));
  }

我們看到我只會用到這個迴圈。如果能直接建立 未簽署的類型:

  for (uint32_t i = 0; i < 6; i++) {
    result.push_back(MakePatternWorkload(
        fxl::StringPrintf("Single bit clear 6-bit (%u/6)", i + 1),
        MultiWordPattern(NegateWords((RotatePattern(every_sixth_bit, i))))));
  }

在三元運算式中呈現多次轉換

如果編譯器在三元運算式中警告運算元,您就理論上 把每個運算元的個別轉換新增到結果類型中 應該比較乾淨,將整個運算式納入轉換中。例如:

[5836/95510] CC obj/src/graphics/lib/compute/spinel/ext/geometry/geometry.arc.c.o
../../src/graphics/lib/compute/spinel/ext/geometry/arc.c:73:50: error: implicit conversion loses floating-point precision: 'double' to 'float' [-Werror,-Wimplicit-float-conversion]
  float const theta_sweep = theta_delta > 0.0f ? SPN_SWEEP_RADIANS : -SPN_SWEEP_RADIANS;
              ~~~~~~~~~~~                        ^~~~~~~~~~~~~~~~~
../../src/graphics/lib/compute/spinel/ext/geometry/arc.c:28:39: note: expanded from macro 'SPN_SWEEP_RADIANS'
#define SPN_SWEEP_RADIANS (2.0 * M_PI / 3.0)  // 120°
                           ~~~~~~~~~~~^~~~~
../../src/graphics/lib/compute/spinel/ext/geometry/arc.c:73:70: error: implicit conversion loses floating-point precision: 'double' to 'float' [-Werror,-Wimplicit-float-conversion]
  float const theta_sweep = theta_delta > 0.0f ? SPN_SWEEP_RADIANS : -SPN_SWEEP_RADIANS;
              ~~~~~~~~~~~                                            ^~~~~~~~~~~~~~~~~~

條件運算式的兩個運算元都會顯示錯誤。演員可以 會套用到運算式中的 SPN_SWEEP_RADIANS 條件運算式。

// Explicit casts at the error sites.
float const theta_sweep = theta_delta > 0.0f ? (float)(SPN_SWEEP_RADIANS) : (float)(-SPN_SWEEP_RADIANS);

// Different AST, but effectively cleaner.
float const theta_sweep = (float)(theta_delta > 0.0f ? SPN_SWEEP_RADIANS : -SPN_SWEEP_RADIANS);

從 AST 的角度來看,運算式並不相同,但邏輯和 最佳化的 Codegen 仍維持不變

嘗試不要在巨集中套用轉換層級,而是在叫用時套用

巨集 (尤其是標頭中的巨集) 常用於多個位置 可接受不同類型的引數建議將轉換值套用到巨集 而不是在巨集內部例如:

../../src/virtualization/third_party/fdt/fdt.c:143:16: error: implicit conversion changes signedness: 'unsigned long' to 'int' [-Werror,-Wsign-conversion]
        *nextoffset = FDT_TAGALIGN(offset);
                    ~ ^~~~~~~~~~~~~~~~~~~~
../../src/virtualization/third_party/fdt/libfdt_internal.h:56:26: note: expanded from macro 'FDT_TAGALIGN'
#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE))
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~
../../src/virtualization/third_party/fdt/libfdt_internal.h:55:40: note: expanded from macro 'FDT_ALIGN'
#define FDT_ALIGN(x, a) (((x) + (a)-1) & ~((a)-1))
                         ~~~~~~~~~~~~~~^~~~~~~~~~
../../src/virtualization/third_party/fdt/fdt.c:143:29: error: implicit conversion changes signedness: 'int' to 'unsigned long' [-Werror,-Wsign-conversion]
        *nextoffset = FDT_TAGALIGN(offset);
                      ~~~~~~~~~~~~~^~~~~~~
../../src/virtualization/third_party/fdt/libfdt_internal.h:56:37: note: expanded from macro 'FDT_TAGALIGN'
#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE))
                         ~~~~~~~~~~~^~~~~~~~~~~~~~~~
../../src/virtualization/third_party/fdt/libfdt_internal.h:55:28: note: expanded from macro 'FDT_ALIGN'
#define FDT_ALIGN(x, a) (((x) + (a)-1) & ~((a)-1))
                           ^  ~

這兩個錯誤都會指向最內層的巨集展開。而不是套用轉換 的第二個錯誤中傳回 x,則會發生錯誤 而是將轉換套用至巨集引數位移,以及 FDT_TAGALIGN。

        *nextoffset = (int)(FDT_TAGALIGN((unsigned)offset));

使用範本類型

以類似於巨集的方式,在範本中套用轉換效果時, 函式,使用的是範本類型,而非警告中指定的類型。 其他程式碼可能會搭配不同類型的程式碼使用這個範本。例如:

template <typename T>
constexpr typename std::make_signed<T>::type ConditionalNegate(
    T x,
    bool is_negative) {
  static_assert(std::is_integral<T>::value, "Type must be integral");
  using SignedT = typename std::make_signed<T>::type;
  using UnsignedT = typename std::make_unsigned<T>::type;
  return static_cast<SignedT>(
      (static_cast<UnsignedT>(x) ^ -SignedT(is_negative)) + is_negative);
}

包括:

../../zircon/third_party/ulib/safemath/include/safemath/safe_conversions_impl.h:75:36: error: implicit conversion changes signedness: 'SignedT' (aka 'long') to 'unsigned long' [-Werror,-Wsign-conversion]
      (static_cast<UnsignedT>(x) ^ -SignedT(is_negative)) + is_negative);
                                 ~ ^~~~~~~~~~~~~~~~~~~~~

我們應該 static_cast 至 UnsignedT 中,而不是無簽署時間。

  return static_cast<SignedT>(
      (static_cast<UnsignedT>(x) ^ static_cast<UnsignedT>(-SignedT(is_negative))) + is_negative);

回呼函式

這可能是特別詐騙的錯誤,因為呼叫堆疊可能不會 即可指出問題所在指派具有 回呼功能的回呼函式 例如引數類型不相符的情況。例如,觀察:

../../sdk/lib/fit/include/lib/fit/function_internal.h:70:19: error: implicit conversion changes signedness: 'long' to 'uint64_t' (aka 'unsigned long') [-Werror,-Wsign-conversion]
    return target(std::forward<Args>(args)...);
           ~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~
../../sdk/lib/fit/include/lib/fit/function_internal.h:91:60: note: in instantiation of member function 'fit::internal::target<(lambda at ../../examples/fidl/test/launcher.cc:67:7), true, false, void, long, fuchsia::sys::TerminationReason>::invoke' requested here
    &unshared_target_type_id, &inline_target_get, &target::invoke, &target::move, &target::destroy};
                                                           ^
../../sdk/lib/fit/include/lib/fit/function_internal.h:370:45: note: in instantiation of static data member 'fit::internal::target<(lambda at ../../examples/fidl/test/launcher.cc:67:7), true, false, void, long, fuchsia::sys::TerminationReason>::ops' requested here
      ops_ = &target_type<DecayedCallable>::ops;
                                            ^
../../sdk/lib/fit/include/lib/fit/function_internal.h:297:5: note: in instantiation of function template specialization 'fit::internal::function_base<16, false, void (long, fuchsia::sys::TerminationReason)>::initialize_target<(lambda at ../../examples/fidl/test/launcher.cc:67:7)>' requested here
    initialize_target(std::forward<Callable>(target));
    ^
../../sdk/lib/fit/include/lib/fit/function.h:253:11: note: in instantiation of function template specialization 'fit::internal::function_base<16, false, void (long, fuchsia::sys::TerminationReason)>::assign<(lambda at ../../examples/fidl/test/launcher.cc:67:7), void>' requested here
    base::assign(std::forward<Callable>(target));
          ^
../../examples/fidl/test/launcher.cc:66:43: note: in instantiation of function template specialization 'fit::function_impl<16, false, void (long, fuchsia::sys::TerminationReason)>::operator=<(lambda at ../../examples/fidl/test/launcher.cc:67:7)>' requested here
  client_controller.events().OnTerminated =
                                          ^

您可能會想查看 sdk/lib/fit/include/lib/fit/function_internal.h:70,但 基本問題位於 sample/fidl/test/launcher.cc:67

  client_controller.events().OnTerminated =
      [&loop, &client_status](uint64_t code, fuchsia::sys::TerminationReason reason) {
...

但只要查看 sdk/fidl/fuchsia.sys/component_controller.fidl OnTerminated 接受使用 int64 做為第一個引數,但我們會指派 lambda 可接受 uint64_t 做為第一個引數。這裡的修正方式是接受 將 int64_t 做為 lambda 引數。

  client_controller.events().OnTerminated =
      [&loop, &client_status](int64_t code, fuchsia::sys::TerminationReason reason) {
...

無損弱點

如果您想向下調節變數,但不希望容許在 「security」和「Safemath」程式庫提供一組公用程式函式 Check_cast,代表發生停機期間資料遺失的情況。

void fun(uint64_t block_number) {
  // Downcast block_number because underlying layer expects uint32_t.
  uint32_t block = safemath::checked_cast<uint32_t>(block_number);
  ....
}

如果 block_number 大於,則宣告 checked_cast 以上 std::numeric_limits<uint32_t>::max()

如要使用安全數學,請在以下項目中加入 //zircon/third_party/ulib/safemath 做為依附元件 BUILD.gn 檔案。請在這裡查看 BUILD.gn 範例 您可以前往這裡查看使用範例。

完成工作

在變更說明中標記封面錯誤,如下所示:

Bug: b/42136089

移除已清除的建構目標的所有參照 //build/config/BUILD.gn,位於「visibility」清單下方 針對 "Wno-conversion" 設定目標

透過擁有者尋找審查人員並合併變更。

範例

贊助者

如有問題或想掌握最新狀態,請與我們聯絡: