使用llvm::ArrayRef配合初始化列表触发ASAN越界,原因何在?
第三种创建mlir::RankedTensorType的方法是否合法?
以下代码展示了三种创建mlir::RankedTensorType的不同方式,使用-fsanitize=address参数编译运行时,前两种方法运行正常,但第三种方法触发了stack-use-after-scope越界错误,请问第三种方法是否合法?
#include <cassert> #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Builders.h" int main(int argc, char* argv[]) { mlir::MLIRContext context; mlir::OpBuilder builder{&context}; llvm::SmallVector<int64_t> vec{1, 2, 3}; llvm::ArrayRef<int64_t> shape_vec{vec}; auto t1 = mlir::RankedTensorType::get(shape_vec, builder.getF32Type()); assert(isa<mlir::RankedTensorType>(t1)); auto t2 = mlir::RankedTensorType::get(llvm::ArrayRef<int64_t>{1, 2, 3}, builder.getF32Type()); assert(isa<mlir::RankedTensorType>(t2)); llvm::ArrayRef<int64_t> shape_il{{1, 2, 3}}; auto t3 = mlir::RankedTensorType::get(shape_il, builder.getF32Type()); assert(isa<mlir::RankedTensorType>(t3)); return 0; }
ASAN报错的部分栈追踪信息如下:
==56088==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffc4efaad00 at pc 0x7eff62d41ba0 bp 0x7ffc4efaa720 sp 0x7ffc4efa9ed0 READ of size 24 at 0x7ffc4efaad00 thread T0 #0 0x7eff62d41b9f in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long) ../../../../libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:861 #1 0x7eff62d422d8 in __interceptor_memcmp ../../../../libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:892 #2 0x7eff62d422d8 in __interceptor_memcmp ../../../../libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:887 #3 0x7eff614c9539 in int std::__memcmp<long, long>(long const*, long const*, unsigned long) /software/tools/gcc/12.3.0/include/c++/12.3.0/bits/stl_algobase.h:105 #4 0x7eff614c9539 in bool std::__equal<true>::equal<long>(long const*, long const*, long const*) /software/tools/gcc/12.3.0/include/c++/12.3.0/bits/stl_algobase.h:1176 #5 0x7eff614c9539 in bool std::__equal_aux1<long const*, long const*>(long const*, long const*, long const*) /software/tools/gcc/12.3.0/include/c++/12.3.0/bits/stl_algobase.h:1210 #6 0x7eff614c9539 in bool std::__equal_aux<long const*, long const*>(long const*, long const*, long const*) /software/tools/gcc/12.3.0/include/c++/12.3.0/bits/stl_algobase.h:1218 #7 0x7eff614c9539 in bool std::equal<long const*, long const*>(long const*, long const*, long const*) /software/tools/gcc/12.3.0/include/c++/12.3.0/bits/stl_algobase.h:1555 #8 0x7eff614c9539 in llvm::ArrayRef<long>::equals(llvm::ArrayRef<long>) const /software/tools/llvm/17.0.2/llvm-project/llvm/include/llvm/ADT/ArrayRef.h:188 #9 0x7eff614c9539 in bool llvm::operator==<long>(llvm::ArrayRef<long>, llvm::ArrayRef<long>) /software/tools/llvm/17.0.2/llvm-project/llvm/include/llvm/ADT/ArrayRef.h:669 #10 0x7eff614c9539 in mlir::detail::RankedTensorTypeStorage::operator==(std::tuple<llvm::ArrayRef<long>, mlir::Type, mlir::Attribute> const&) const tools/mlir/include/mlir/IR/BuiltinTypes.cpp.inc:239 #11 0x7eff614c9539 in mlir::StorageUniquer::get<mlir::detail::RankedTensorTypeStorage, llvm::ArrayRef<long>&, mlir::Type&, mlir::Attribute&>(llvm::function_ref<void (mlir::detail::RankedTensorTypeStorage*)>, mlir::TypeID, llvm::ArrayRef<long>&, mlir::Type&, mlir::Attribute&)::{lambda(mlir::StorageUniquer::BaseStorage const*)#1}::operator()(mlir::StorageUniquer::BaseStorage const*) const /software/tools/llvm/17.0.2/llvm-project/mlir/include/mlir/Support/StorageUniquer.h:205
结论
第三种写法不合法,会触发未定义行为。
原因分析
llvm::ArrayRef是轻量级视图类,本身不存储数据,仅持有指向数据的指针和长度。第三种写法中:
llvm::ArrayRef<int64_t> shape_il{{1, 2, 3}};
双层大括号会创建一个临时的std::initializer_list<int64_t>,ArrayRef会绑定到这个临时对象。但根据C++标准,临时对象的生命周期仅持续到当前完整表达式结束——也就是这行代码执行完毕后,临时初始化列表就会被销毁,shape_il内部的指针变为悬空指针。
后续调用mlir::RankedTensorType::get时,MLIR的StorageUniquer会对类型做唯一性校验(栈追踪中的operator==调用可证明),此时会访问shape_il指向的已销毁栈内存,从而触发ASAN的stack-use-after-scope错误。
对比合法写法
- 第一种写法:
ArrayRef绑定到SmallVector,SmallVector的生命周期覆盖了后续的get调用,指针始终有效。 - 第二种写法:在函数参数位置创建
ArrayRef<int64_t>{1,2,3}临时对象,根据C++标准,临时对象的生命周期会延长至函数调用结束,因此get执行期间指针有效,不会出错。
修复方式
若要使用类似第三种的写法,需确保数据生命周期足够长,可以显式创建持久化容器:
// 方式1:显式创建initializer_list延长生命周期 std::initializer_list<int64_t> init{1,2,3}; llvm::ArrayRef<int64_t> shape_il(init); // 方式2:使用SmallVector(推荐,更符合LLVM生态习惯) llvm::SmallVector<int64_t> shape_vec{1,2,3}; llvm::ArrayRef<int64_t> shape_il(shape_vec);
内容的提问来源于stack exchange,提问作者MarkB
相关产品推荐
相关产品推荐

