blob: 9ddac6d32a681f04f70b008bdbf26a4b00a68c96 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/traffic_annotation/auditor/instance.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "third_party/protobuf/src/google/protobuf/io/tokenizer.h"
#include "third_party/protobuf/src/google/protobuf/text_format.h"
#include "tools/traffic_annotation/auditor/traffic_annotation_auditor.h"
namespace {
// This class receives parsing errors from google::protobuf::TextFormat::Parser
// which is used during protobuf deserialization.
class SimpleErrorCollector : public google::protobuf::io::ErrorCollector {
public:
SimpleErrorCollector(int proto_starting_line)
: google::protobuf::io::ErrorCollector(),
line_offset_(proto_starting_line) {}
~SimpleErrorCollector() override = default;
void AddError(int line,
google::protobuf::io::ColumnNumber column,
const std::string& message) override {
AddMessage(line, column, message);
}
void AddWarning(int line,
google::protobuf::io::ColumnNumber column,
const std::string& message) override {
AddMessage(line, column, message);
}
std::string GetMessage() { return message_; }
private:
void AddMessage(int line,
google::protobuf::io::ColumnNumber column,
const std::string& message) {
message_ += base::StringPrintf(
"%sLine %i, column %i, %s", message_.length() ? " " : "",
line_offset_ + line, static_cast<int>(column), message.c_str());
}
std::string message_;
int line_offset_;
};
// This macro merges the content of one string field from two annotations.
// DST->FLD is the destination field, and SRD->FLD is the source field.
#define MERGE_STRING_FIELDS(SRC, DST, FLD) \
if (!SRC.FLD().empty()) { \
if (!DST->FLD().empty()) { \
DST->set_##FLD(base::StringPrintf("%s\n%s", SRC.FLD().c_str(), \
DST->FLD().c_str())); \
} else { \
DST->set_##FLD(SRC.FLD()); \
} \
}
std::map<int, std::string> kSemanticsFields = {
{traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kSenderFieldNumber,
"semantics::sender"},
{traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kDescriptionFieldNumber,
"semantics::description"},
{traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kTriggerFieldNumber,
"semantics::trigger"},
{traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kDataFieldNumber,
"semantics::data"},
{traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kDestinationFieldNumber,
"semantics::destination"},
};
std::map<int, std::string> kPolicyFields = {
{traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kCookiesAllowedFieldNumber,
"policy::cookies_allowed"},
{traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kCookiesStoreFieldNumber,
"policy::cookies_store"},
{traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kSettingFieldNumber,
"policy::setting"},
{traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kChromePolicyFieldNumber,
"policy::chrome_policy"},
{traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kPolicyExceptionJustificationFieldNumber,
"policy::policy_exception_justification"},
};
std::vector<int> kChromePolicyFields = {
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kChromePolicyFieldNumber,
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kPolicyExceptionJustificationFieldNumber};
} // namespace
AnnotationInstance::AnnotationInstance()
: type(Type::ANNOTATION_COMPLETE),
unique_id_hash_code(0),
second_id_hash_code(0),
archive_content_hash_code(0),
is_loaded_from_archive(false),
is_merged(false) {}
AnnotationInstance::AnnotationInstance(const AnnotationInstance& other)
: proto(other.proto),
type(other.type),
second_id(other.second_id),
unique_id_hash_code(other.unique_id_hash_code),
second_id_hash_code(other.second_id_hash_code),
archive_content_hash_code(other.archive_content_hash_code),
is_loaded_from_archive(other.is_loaded_from_archive),
is_merged(other.is_merged) {}
AuditorResult AnnotationInstance::Deserialize(
const std::vector<std::string>& serialized_lines,
int start_line,
int end_line) {
if (end_line - start_line < 7) {
return AuditorResult(AuditorResult::Type::ERROR_FATAL,
"Not enough lines to deserialize annotation.");
}
// Extract header lines.
const std::string& file_path = serialized_lines[start_line++];
const std::string& function_context = serialized_lines[start_line++];
int line_number;
base::StringToInt(serialized_lines[start_line++], &line_number);
std::string function_type = serialized_lines[start_line++];
const std::string& unique_id = serialized_lines[start_line++];
second_id = serialized_lines[start_line++];
// Decode function type.
if (function_type == "Definition") {
type = Type::ANNOTATION_COMPLETE;
} else if (function_type == "Partial") {
type = Type::ANNOTATION_PARTIAL;
} else if (function_type == "Completing") {
type = Type::ANNOTATION_COMPLETING;
} else if (function_type == "BranchedCompleting") {
type = Type::ANNOTATION_BRANCHED_COMPLETING;
} else {
return AuditorResult(AuditorResult::Type::ERROR_FATAL,
base::StringPrintf("Unexpected function type: %s",
function_type.c_str()));
}
// Process test tags.
unique_id_hash_code = TrafficAnnotationAuditor::ComputeHashValue(unique_id);
if (unique_id_hash_code == TRAFFIC_ANNOTATION_FOR_TESTS.unique_id_hash_code ||
unique_id_hash_code ==
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS.unique_id_hash_code) {
return AuditorResult(AuditorResult::Type::ERROR_TEST_ANNOTATION, "",
file_path, line_number);
}
// Process undefined tags.
if (unique_id_hash_code == NO_TRAFFIC_ANNOTATION_YET.unique_id_hash_code ||
unique_id_hash_code ==
NO_PARTIAL_TRAFFIC_ANNOTATION_YET.unique_id_hash_code) {
return AuditorResult(AuditorResult::Type::ERROR_NO_ANNOTATION, "",
file_path, line_number);
}
// Process missing tag.
if (unique_id_hash_code == MISSING_TRAFFIC_ANNOTATION.unique_id_hash_code)
return AuditorResult(AuditorResult::Type::ERROR_MISSING_TAG_USED, "",
file_path, line_number);
// Decode serialized proto.
std::string annotation_text = "";
while (start_line < end_line) {
annotation_text += serialized_lines[start_line++] + "\n";
}
SimpleErrorCollector error_collector(line_number);
google::protobuf::TextFormat::Parser parser;
parser.RecordErrorsTo(&error_collector);
if (!parser.ParseFromString(annotation_text,
(google::protobuf::Message*)&proto)) {
return AuditorResult(AuditorResult::Type::ERROR_SYNTAX,
error_collector.GetMessage().c_str(), file_path,
line_number);
}
// Add other fields.
traffic_annotation::NetworkTrafficAnnotation_TrafficSource* src =
proto.mutable_source();
src->set_file(file_path);
src->set_function(function_context);
src->set_line(line_number);
proto.set_unique_id(unique_id);
second_id_hash_code = TrafficAnnotationAuditor::ComputeHashValue(second_id);
return AuditorResult(AuditorResult::Type::RESULT_OK);
}
// Returns the proto field numbers of TrafficSemantics.
void AnnotationInstance::GetSemanticsFieldNumbers(
std::set<int>* field_numbers) const {
field_numbers->clear();
const traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics
semantics = proto.semantics();
if (!semantics.sender().empty())
field_numbers->insert(semantics.kSenderFieldNumber);
if (!semantics.description().empty())
field_numbers->insert(semantics.kDescriptionFieldNumber);
if (!semantics.trigger().empty())
field_numbers->insert(semantics.kTriggerFieldNumber);
if (!semantics.data().empty())
field_numbers->insert(semantics.kDataFieldNumber);
if (semantics.destination() !=
traffic_annotation::
NetworkTrafficAnnotation_TrafficSemantics_Destination_UNSPECIFIED) {
field_numbers->insert(semantics.kDestinationFieldNumber);
}
}
// Returns the proto field numbers of TrafficPolicy.
void AnnotationInstance::GetPolicyFieldNumbers(
std::set<int>* field_numbers) const {
field_numbers->clear();
const traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy policy =
proto.policy();
// If cookies are not allowed, the negated value of the
// kCookiesAllowedFieldNumber is returned. As field numbers are positive, this
// will not collide with any other value.
if (policy.cookies_allowed() ==
traffic_annotation::
NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_YES) {
field_numbers->insert(policy.kCookiesAllowedFieldNumber);
} else if (policy.cookies_allowed() ==
traffic_annotation::
NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO) {
field_numbers->insert(-policy.kCookiesAllowedFieldNumber);
}
if (!policy.cookies_store().empty())
field_numbers->insert(policy.kCookiesStoreFieldNumber);
if (!policy.setting().empty())
field_numbers->insert(policy.kSettingFieldNumber);
if (policy.chrome_policy_size())
field_numbers->insert(policy.kChromePolicyFieldNumber);
if (!policy.policy_exception_justification().empty())
field_numbers->insert(policy.kPolicyExceptionJustificationFieldNumber);
}
// Checks if an annotation has all required fields.
AuditorResult AnnotationInstance::IsComplete() const {
std::vector<std::string> unspecifieds;
std::string extra_texts;
std::set<int> fields;
GetSemanticsFieldNumbers(&fields);
for (const auto& item : kSemanticsFields) {
if (!base::ContainsKey(fields, item.first))
unspecifieds.push_back(item.second);
}
GetPolicyFieldNumbers(&fields);
for (const auto& item : kPolicyFields) {
if (!base::ContainsKey(fields, item.first)) {
// If 'cookies_allowed = NO' is provided, ignore not having
// 'cookies_allowed = YES'.
if (item.first ==
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kCookiesAllowedFieldNumber &&
base::ContainsKey(fields, -item.first))
continue;
// If |cookies_store| is not provided, ignore if 'cookies_allowed = NO' is
// in the list.
if (item.first ==
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kCookiesStoreFieldNumber &&
base::ContainsKey(
fields,
-traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kCookiesAllowedFieldNumber))
continue;
// If either of |chrome_policy| or |policy_exception_justification| are
// avaliable, ignore not having the other one.
if (base::ContainsValue(kChromePolicyFields, item.first) &&
(base::ContainsKey(fields, kChromePolicyFields[0]) ||
base::ContainsKey(fields, kChromePolicyFields[1]))) {
continue;
}
unspecifieds.push_back(item.second);
}
}
if (!unspecifieds.size())
return AuditorResult(AuditorResult::Type::RESULT_OK);
std::string error_text;
for (const std::string& item : unspecifieds)
error_text += item + ", ";
error_text = error_text.substr(0, error_text.length() - 2);
return AuditorResult(AuditorResult::Type::ERROR_INCOMPLETE_ANNOTATION,
error_text, proto.source().file(),
proto.source().line());
}
// Checks if annotation fields are consistent.
AuditorResult AnnotationInstance::IsConsistent() const {
const traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy policy =
proto.policy();
if (policy.cookies_allowed() ==
traffic_annotation::
NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO &&
policy.cookies_store().size()) {
return AuditorResult(
AuditorResult::Type::ERROR_INCONSISTENT_ANNOTATION,
"Cookies store is specified while cookies are not allowed.",
proto.source().file(), proto.source().line());
}
if (policy.chrome_policy_size() &&
policy.policy_exception_justification().size()) {
return AuditorResult(
AuditorResult::Type::ERROR_INCONSISTENT_ANNOTATION,
"Both chrome policies and policy exception justification are present.",
proto.source().file(), proto.source().line());
}
return AuditorResult(AuditorResult::Type::RESULT_OK);
}
bool AnnotationInstance::IsCompletableWith(
const AnnotationInstance& other) const {
if (type != AnnotationInstance::Type::ANNOTATION_PARTIAL || second_id.empty())
return false;
if (other.type == AnnotationInstance::Type::ANNOTATION_COMPLETING) {
return second_id_hash_code == other.unique_id_hash_code;
} else if (other.type ==
AnnotationInstance::Type::ANNOTATION_BRANCHED_COMPLETING) {
return second_id_hash_code == other.second_id_hash_code;
} else {
return false;
}
}
AuditorResult AnnotationInstance::CreateCompleteAnnotation(
AnnotationInstance& completing_annotation,
AnnotationInstance* combination) const {
DCHECK(IsCompletableWith(completing_annotation));
// To keep the source information meta data, if completing annotation is of
// type COMPLETING, keep |this| as the main and the other as completing.
// But if compliting annotation is of type BRANCHED_COMPLETING, reverse
// the order.
const AnnotationInstance* other;
if (completing_annotation.type ==
AnnotationInstance::Type::ANNOTATION_COMPLETING) {
*combination = *this;
other = &completing_annotation;
} else {
*combination = completing_annotation;
other = this;
}
combination->is_merged = true;
combination->type = AnnotationInstance::Type::ANNOTATION_COMPLETE;
combination->second_id.clear();
combination->second_id_hash_code = 0;
// Update comment.
std::string new_comments = combination->proto.comments();
if (!other->proto.comments().empty()) {
if (!new_comments.empty())
new_comments += "\n";
new_comments += other->proto.comments();
}
if (!new_comments.empty())
new_comments += "\n";
new_comments += base::StringPrintf(
"This annotation is a merge of the following two annotations:\n"
"'%s' in '%s:%i' and '%s' in '%s:%i'.",
proto.unique_id().c_str(), proto.source().file().c_str(),
proto.source().line(), completing_annotation.proto.unique_id().c_str(),
completing_annotation.proto.source().file().c_str(),
completing_annotation.proto.source().line());
combination->proto.set_comments(new_comments);
// Copy TrafficSemantics.
const traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics
src_semantics = other->proto.semantics();
traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics* dst_semantics =
combination->proto.mutable_semantics();
MERGE_STRING_FIELDS(src_semantics, dst_semantics, sender);
MERGE_STRING_FIELDS(src_semantics, dst_semantics, description);
MERGE_STRING_FIELDS(src_semantics, dst_semantics, trigger);
MERGE_STRING_FIELDS(src_semantics, dst_semantics, data);
MERGE_STRING_FIELDS(src_semantics, dst_semantics, destination_other);
// If destination is not specified in dst_semantics, get it from
// src_semantics. If both are specified and they differ, issue error.
if (dst_semantics->destination() ==
traffic_annotation::
NetworkTrafficAnnotation_TrafficSemantics_Destination_UNSPECIFIED) {
dst_semantics->set_destination(src_semantics.destination());
} else if (
src_semantics.destination() !=
traffic_annotation::
NetworkTrafficAnnotation_TrafficSemantics_Destination_UNSPECIFIED &&
src_semantics.destination() != dst_semantics->destination()) {
AuditorResult error(
AuditorResult::Type::ERROR_MERGE_FAILED,
"Annotations contain different semantics::destination values.");
error.AddDetail(proto.unique_id());
error.AddDetail(completing_annotation.proto.unique_id());
return error;
}
// Copy TrafficPolicy.
const traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy src_policy =
other->proto.policy();
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy* dst_policy =
combination->proto.mutable_policy();
MERGE_STRING_FIELDS(src_policy, dst_policy, cookies_store);
MERGE_STRING_FIELDS(src_policy, dst_policy, setting);
// Set cookies_allowed to the superseding value of both.
dst_policy->set_cookies_allowed(
std::max(dst_policy->cookies_allowed(), src_policy.cookies_allowed()));
DCHECK_GT(traffic_annotation::
NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_YES,
traffic_annotation::
NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO);
DCHECK_GT(
traffic_annotation::
NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO,
traffic_annotation::
NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_UNSPECIFIED);
for (int i = 0; i < src_policy.chrome_policy_size(); i++)
*dst_policy->add_chrome_policy() = src_policy.chrome_policy(i);
if (!src_policy.policy_exception_justification().empty()) {
if (!dst_policy->policy_exception_justification().empty()) {
dst_policy->set_policy_exception_justification(
dst_policy->policy_exception_justification() + "\n");
}
dst_policy->set_policy_exception_justification(
dst_policy->policy_exception_justification() +
src_policy.policy_exception_justification());
}
return AuditorResult::Type::RESULT_OK;
}
int AnnotationInstance::GetContentHashCode() const {
if (is_loaded_from_archive)
return archive_content_hash_code;
traffic_annotation::NetworkTrafficAnnotation source_free_proto = proto;
source_free_proto.clear_source();
std::string content;
google::protobuf::TextFormat::PrintToString(source_free_proto, &content);
return TrafficAnnotationAuditor::ComputeHashValue(content);
}
// static
AnnotationInstance AnnotationInstance::LoadFromArchive(
AnnotationInstance::Type type,
const std::string& unique_id,
int unique_id_hash_code,
int second_id_hash_code,
int content_hash_code,
const std::set<int>& semantics_fields,
const std::set<int>& policy_fields,
const std::string& file_path) {
AnnotationInstance annotation;
annotation.is_loaded_from_archive = true;
annotation.type = type;
annotation.proto.set_unique_id(unique_id);
annotation.proto.mutable_source()->set_file(file_path);
annotation.unique_id_hash_code = unique_id_hash_code;
if (annotation.NeedsTwoIDs()) {
annotation.second_id_hash_code = second_id_hash_code;
// As we don't have the actual second id, a generated value is written to
// ensure that the field is not empty. Current set of auditor tests and
// unittests just check if this field is not empty when a second id is
// required. Tests that are based on matching the ids (like
// partial/completing annotations) are based on the hash codes.
annotation.second_id =
base::StringPrintf("ARCHIVED_ID_%i", annotation.second_id_hash_code);
}
annotation.archive_content_hash_code = content_hash_code;
// The values of the semantics and policy are set so that the tests would know
// which fields were available before archive.
if (base::ContainsKey(
semantics_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kSenderFieldNumber)) {
annotation.proto.mutable_semantics()->set_sender("[Archived]");
}
if (base::ContainsKey(
semantics_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kDescriptionFieldNumber)) {
annotation.proto.mutable_semantics()->set_description("[Archived]");
}
if (base::ContainsKey(
semantics_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kTriggerFieldNumber)) {
annotation.proto.mutable_semantics()->set_trigger("[Archived]");
}
if (base::ContainsKey(
semantics_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kDataFieldNumber)) {
annotation.proto.mutable_semantics()->set_data("[Archived]");
}
if (base::ContainsKey(
semantics_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
kDestinationFieldNumber)) {
annotation.proto.mutable_semantics()->set_destination(
traffic_annotation::
NetworkTrafficAnnotation_TrafficSemantics_Destination_WEBSITE);
}
if (base::ContainsKey(
policy_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kCookiesAllowedFieldNumber)) {
annotation.proto.mutable_policy()->set_cookies_allowed(
traffic_annotation::
NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_YES);
}
if (base::ContainsKey(
policy_fields,
-traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kCookiesAllowedFieldNumber)) {
annotation.proto.mutable_policy()->set_cookies_allowed(
traffic_annotation::
NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO);
}
if (base::ContainsKey(
policy_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kCookiesStoreFieldNumber)) {
annotation.proto.mutable_policy()->set_cookies_store("[Archived]");
}
if (base::ContainsKey(
policy_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kSettingFieldNumber)) {
annotation.proto.mutable_policy()->set_setting("[Archived]");
}
if (base::ContainsKey(
policy_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kChromePolicyFieldNumber)) {
annotation.proto.mutable_policy()->add_chrome_policy();
}
if (base::ContainsKey(
policy_fields,
traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
kPolicyExceptionJustificationFieldNumber)) {
annotation.proto.mutable_policy()->set_policy_exception_justification(
"[Archived]");
}
return annotation;
}
std::string AnnotationInstance::Serialize() const {
std::string text;
std::set<int> fields;
text = base::StringPrintf(
"{\tType: %i\n"
"\tID: %s\n"
"\tHashcode 1: %i\n"
"\tHashcode 2: %i\n"
"\tFrom Archive: %i\n"
"\tSource File: %s\n",
static_cast<int>(type), proto.unique_id().c_str(), unique_id_hash_code,
second_id_hash_code, is_loaded_from_archive,
proto.source().file().c_str());
GetSemanticsFieldNumbers(&fields);
text += "\tSemantics: ";
for (int i : fields)
text += base::StringPrintf("%s,", kSemanticsFields[i].c_str());
GetPolicyFieldNumbers(&fields);
text += "\n\tPolicies: ";
for (int i : fields) {
text += base::StringPrintf("%s%s,", i < 0 ? "-" : "",
kPolicyFields[abs(i)].c_str());
}
text += "\n}";
return text;
}
std::ostream& operator<<(std::ostream& out,
const AnnotationInstance& instance) {
return out << instance.Serialize();
}
CallInstance::CallInstance() : line_number(0), is_annotated(false) {}
CallInstance::CallInstance(const CallInstance& other)
: file_path(other.file_path),
line_number(other.line_number),
function_context(other.function_context),
function_name(other.function_name),
is_annotated(other.is_annotated) {}
AuditorResult CallInstance::Deserialize(
const std::vector<std::string>& serialized_lines,
int start_line,
int end_line) {
if (end_line - start_line != 5) {
return AuditorResult(AuditorResult::Type::ERROR_FATAL,
"Not enough lines to deserialize call.");
}
file_path = serialized_lines[start_line++];
function_context = serialized_lines[start_line++];
int line_number_int;
base::StringToInt(serialized_lines[start_line++], &line_number_int);
line_number = static_cast<uint32_t>(line_number_int);
function_name = serialized_lines[start_line++];
int is_annotated_int;
base::StringToInt(serialized_lines[start_line++], &is_annotated_int);
is_annotated = is_annotated_int != 0;
return AuditorResult(AuditorResult::Type::RESULT_OK);
}
AssignmentInstance::AssignmentInstance() : line_number(0) {}
AssignmentInstance::AssignmentInstance(const AssignmentInstance& other)
: file_path(other.file_path),
line_number(other.line_number),
function_context(other.function_context) {}
AuditorResult AssignmentInstance::Deserialize(
const std::vector<std::string>& serialized_lines,
int start_line,
int end_line) {
if (end_line - start_line != 3) {
return AuditorResult(AuditorResult::Type::ERROR_FATAL,
"Not enough lines to deserialize assignment.");
}
file_path = serialized_lines[start_line++];
function_context = serialized_lines[start_line++];
int line_number_int;
base::StringToInt(serialized_lines[start_line++], &line_number_int);
line_number = static_cast<uint32_t>(line_number_int);
return AuditorResult(AuditorResult::Type::RESULT_OK);
}