目標與動力
C/C++ 允許轉換類型之間的值,而這可能會修改基礎值,例如溢位、反向溢位、符號變更、截斷和精確度下降等因素。
這些轉換可能會間接發生,在這種情況下,系統不會指示作者預期會發生轉換或明確指出轉換是有意進行轉換,而作者也清楚知道結果。
根據預設,Fuchsia 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" ]
}
在 "Wno-conversion"
設定目標的 visibility
清單下方的 //build/config/BUILD.gn
中,您可以找到來源樹狀結構中含有抑制的所有執行個體的許可清單。
如何提供幫助
選取工作
挑選任何參照 Wno-conversion
或 bug 58162 的執行個體。視需要瀏覽 //build/config/BUILD.gn
,以參照熟悉的所有程式碼。
您可以忽略第三方程式碼 (目錄路徑中的 third_party
)。Fuchsia 專案並未擁有這組程式碼,因此無法修正。也就是說,如果您想前往上游程式碼位置,並做出修正即可。也就是說,上游維護人員也必須同意啟用 -Wconversion
旗標,否則就會遭到迴歸。
執行工作
移除 -Wno-conversion
隱藏功能、重新建構,並修正所有錯誤。
請注意,有時可能不會顯示任何錯誤。發生這種情況時,通常是因為其他使用者對原始碼進行變更,藉此含有隱含類型轉換問題,但並未移除隱藏。如果沒有需要修正的地方,請將變更項目送審。如果尚未回答,請繼續閱讀。
大幅減少程式碼的重大可能性,並已無其必要。如果您移除了大量抑制內容且變更通過 CQ,那就沒什麼錯誤了。
例子:
- 478402:[清理] 移除未使用的 -Wno-conversions
- 474400:[bt][common] 移除不轉換抑制
- 487186:[連線] 清除無效轉換
- 484416:[儲存空間] 延遲轉換
簡易精簡
[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 中的程式碼,但根本問題位於 examples/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 做為第一個引數,但會指派一個可接受 uint64_t 做為第一個引數的 lambda。解決方法是改為接受 int64_t 做為 lambda 引數。
client_controller.events().OnTerminated =
[&loop, &client_status](int64_t code, fuchsia::sys::TerminationReason reason) {
...
無損下移
如果您想將變數降級,但不想容許轉換期間發生遺失損失,Safemath 程式庫會提供一組公用函式 (包括 checked_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 大於 std::numeric_limits<uint32_t>::max()
,則為 checked_cast
宣告。
如要使用 Safemath,請在 BUILD.gn
檔案中將 //zircon/third_party/ulib/safemath
新增為依附元件。您可以前往這裡查看 BUILD.gn 範例,以及這裡的用法範例。
完成工作
請在變更說明中標記封面錯誤,如下所示:
Bug: 58162
移除您在 //build/config/BUILD.gn
中清除的建構目標參照 (位於 "Wno-conversion"
設定目標的 visibility
清單下方)。
向擁有者尋找審查者並合併變更。
範例
- 469454:[偵錯工具] 修正 -Wconversion 問題
- 464514:[fit, fzl] 啟用 -Wconversion 警告
- 463856:[kcounter] 啟用 -Wconversion 警告
- 456573:[意見回饋] 修正 -Wconversion 錯誤
- 450719:[camera][lib] 修正 -Wconversion 版本錯誤
贊助者
提問或詢問最新進度: