mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 22:02:52 +01:00
Per #257, we should be treating unformedness as all-or-nothing, rather than being a per-field or per-array-element property. Previously we initialized an array with no explicit initializer as containing a sequence of uninitialized values, but that led to crashes when attempting to access those values, as the checks for reading an uninitialized value only expected values to be uninitialized at the top level. Also, we had existing tests that attempt to store to an element of an uninitialized array. We now detect that and treat it as UB during evaluation, rather than crashing due to trying to perform field access into an uninitialized value. Finally, many of these problems can be detected statically, but the resolve_unformed pass wasn't catching them because it missed a few expression and declaration forms. Support for those cases has been added too. This causes the pass to recurse more often, and in particular our existing recursion test started hitting a stack overflow after this, so resolve_unformed now uses `RunWithExtraStack`. In passing, remove the need to explicitly tell `RunWithExtraStack` the return type, and infer it as the return type of the callable instead.
112 lines
3.8 KiB
C++
112 lines
3.8 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#include "explorer/interpreter/heap.h"
|
|
|
|
#include "explorer/ast/value.h"
|
|
#include "explorer/common/error_builders.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Error.h"
|
|
|
|
namespace Carbon {
|
|
|
|
auto Heap::AllocateValue(Nonnull<const Value*> v) -> AllocationId {
|
|
// Putting the following two side effects together in this function
|
|
// ensures that we don't do anything else in between, which would be really
|
|
// bad! Consider whether to include a copy of the input v in this function or
|
|
// to leave it up to the caller.
|
|
AllocationId a(values_.size());
|
|
values_.push_back(v);
|
|
if (v->kind() == Carbon::Value::Kind::UninitializedValue) {
|
|
states_.push_back(ValueState::Uninitialized);
|
|
} else {
|
|
states_.push_back(ValueState::Alive);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
auto Heap::Read(const Address& a, SourceLocation source_loc) const
|
|
-> ErrorOr<Nonnull<const Value*>> {
|
|
CARBON_RETURN_IF_ERROR(this->CheckInit(a.allocation_, source_loc));
|
|
CARBON_RETURN_IF_ERROR(this->CheckAlive(a.allocation_, source_loc));
|
|
Nonnull<const Value*> value = values_[a.allocation_.index_];
|
|
return value->GetElement(arena_, a.element_path_, source_loc, value);
|
|
}
|
|
|
|
auto Heap::Write(const Address& a, Nonnull<const Value*> v,
|
|
SourceLocation source_loc) -> ErrorOr<Success> {
|
|
CARBON_RETURN_IF_ERROR(this->CheckAlive(a.allocation_, source_loc));
|
|
if (states_[a.allocation_.index_] == ValueState::Uninitialized) {
|
|
if (!a.element_path_.IsEmpty()) {
|
|
return ProgramError(source_loc)
|
|
<< "undefined behavior: store to subobject of uninitialized value "
|
|
<< *values_[a.allocation_.index_];
|
|
}
|
|
states_[a.allocation_.index_] = ValueState::Alive;
|
|
}
|
|
CARBON_ASSIGN_OR_RETURN(values_[a.allocation_.index_],
|
|
values_[a.allocation_.index_]->SetField(
|
|
arena_, a.element_path_, v, source_loc));
|
|
return Success();
|
|
}
|
|
|
|
auto Heap::GetAllocationId(Nonnull<const Value*> v) const
|
|
-> std::optional<AllocationId> {
|
|
auto iter = std::find(values_.begin(), values_.end(), v);
|
|
if (iter != values_.end()) {
|
|
auto index = iter - values_.begin();
|
|
if (states_[index] == ValueState::Alive) {
|
|
return AllocationId(index);
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) const
|
|
-> ErrorOr<Success> {
|
|
if (states_[allocation.index_] == ValueState::Dead) {
|
|
return ProgramError(source_loc)
|
|
<< "undefined behavior: access to dead value "
|
|
<< *values_[allocation.index_];
|
|
}
|
|
return Success();
|
|
}
|
|
|
|
auto Heap::CheckInit(AllocationId allocation, SourceLocation source_loc) const
|
|
-> ErrorOr<Success> {
|
|
if (states_[allocation.index_] == ValueState::Uninitialized) {
|
|
return ProgramError(source_loc)
|
|
<< "undefined behavior: access to uninitialized value "
|
|
<< *values_[allocation.index_];
|
|
}
|
|
return Success();
|
|
}
|
|
|
|
void Heap::Deallocate(AllocationId allocation) {
|
|
if (states_[allocation.index_] != ValueState::Dead) {
|
|
states_[allocation.index_] = ValueState::Dead;
|
|
} else {
|
|
CARBON_FATAL() << "deallocating an already dead value: "
|
|
<< *values_[allocation.index_];
|
|
}
|
|
}
|
|
|
|
void Heap::Deallocate(const Address& a) { Deallocate(a.allocation_); }
|
|
|
|
void Heap::Print(llvm::raw_ostream& out) const {
|
|
llvm::ListSeparator sep;
|
|
for (size_t i = 0; i < values_.size(); ++i) {
|
|
out << sep;
|
|
out << i << ": ";
|
|
if (states_[i] == ValueState::Uninitialized) {
|
|
out << "!";
|
|
} else if (states_[i] == ValueState::Dead) {
|
|
out << "!!";
|
|
}
|
|
out << *values_[i];
|
|
}
|
|
}
|
|
|
|
} // namespace Carbon
|