You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Create a workspace and expense report on the workspace
Set the default report title to "Report field value {field:Test text}"
Create a text report field on the policy named "Test text" with the initial value set to "initial"
Update the initial value to "{field:Test text}"
Open the initial value page again
Delete the report field
Open the policy expense chat
Expected result
Setting the initial value of a report field, or the value of a report field, to a text value which is a formula referencing the field itself, and therefore creating a circular reference, should result in an error and the new value should not be saved
Note: On Classic, setting the initial value of a report field to a valid formula makes the report field a "formula" type, and its value can not be manually changed on the report.
If a report field initial value is a formula that references itself, and it is already saved in the database, because it is currently allowed on Expensify Classic, then the type of the returned report field should be changed to "text" so that it is not computed
The initial value page is not blank, and when opening the policy expense chat the app does not crash
Actual result
The initial value page is blank, and the app crashes when opening the policy expense chat
2025-10-01_08-10-53.mp4Backend code for identifying circular references
First circular references are checked for the whole formula, but on the frontend you could check passing only one "part" such as {field:someField}, and no parent fields.
string Formula::compute(SQLite& db, constJSON::Value& reportJSON, const UserDefinedFieldFormula* formula,
map<int64_t, UserCache>* userCacheMap, map<string, PolicyCache>* policyCacheMap, map<int64_t, ReportCache>* reportCacheMap, constJSON::Value& transactionJSON, constbool preventDeprecatedFormulas)
{
SDEBUG("[Formula::compute] defaultValue: " << formula->getDefaultValue().value());
constauto parts = parse(formula->getFormula());
constint64_t reportID = reportJSON.getIntMemberWithDefault("reportID", 0);
if (hasCircularReferences(db, reportID, parts, {})) {
SINFO("Formula has circular references, omitting");
return"";
}
boolFormula::hasCircularReferences(SQLite& db, int64_t reportID, const vector<unique_ptr<FormulaPart>>& parts, const vector<string>& parentFields)
{
// Prevent excessive recursion depth that could cause stack overflowif (parentFields.size() > MAX_CIRCULAR_REFERENCE_DEPTH) {
SHMMM("Circular reference depth exceeds maximum allowed", {{"depth", to_string(parentFields.size())}, {"maxDepth", to_string(MAX_CIRCULAR_REFERENCE_DEPTH)}});
// Assume circular reference to prevent further recursionreturntrue;
}
for (constauto& part : parts) {
if (part->getType() != FormulaPart::TYPE_FIELD) {
continue;
}
auto* fieldPart = static_cast<FieldPart*>(part.get());
const optional<unique_ptr<UserDefinedField>>& field = fieldPart->getField(db, reportID);
if (!field.has_value()) {
continue;
}
auto* formulaField = dynamic_cast<UserDefinedFieldFormula*>(field.value().get());
if (!formulaField) {
continue;
}
const string fieldID = formulaField->getID();
if (find(parentFields.begin(), parentFields.end(), fieldID) != parentFields.end()) {
returntrue;
}
vector<string> newParentFields = parentFields;
newParentFields.push_back(fieldID);
if (formulaField->hasCircularReferences(db, reportID, newParentFields)) {
returntrue;
}
}
returnfalse;
}
optional<unique_ptr<UserDefinedField>> FieldPart::getField(SQLite& db, int64_t reportID)
{
if (fieldPath.size() == 0) {
returnnullopt;
}
string name = fieldPath[0];
auto fieldID = UserDefinedField::createFromDatabaseData<UserDefinedField>(JSON::Value({{"name", STrim(name)}, {"type", UserDefinedField::UDF_TYPE_TEXT}}))->getID();
auto udfs = Report::getUserDefinedFields(db, reportID);
for (auto& udf : udfs) {
if (udf->getID() == fieldID) {
returnmake_optional(move(udf));
}
}
returnnullopt;
}
Issue Reported by : Applause Internal Team
Action performed
Expected result
The initial value page is not blank, and when opening the policy expense chat the app does not crash
Actual result
The initial value page is blank, and the app crashes when opening the policy expense chat
490105214-64e7dfec-1f75-472e-92ae-1bbab24580e6.mp4
OldDot behavior
2025-10-01_08-10-53.mp4
Backend code for identifying circular references
First circular references are checked for the whole formula, but on the frontend you could check passing only one "part" such as
{field:someField}, and no parent fields.Issue Owner
Current Issue Owner: @sonialiap