/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "utils/regex-match.h"
#include <memory>
#include "annotator/types.h"
#include "utils/lua-utils.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "lauxlib.h"
#include "lualib.h"
#ifdef __cplusplus
}
#endif
namespace libtextclassifier3 {
namespace {
// Provide a lua environment for running regex match post verification.
// It sets up and exposes the match data as well as the context.
class LuaVerifier : private LuaEnvironment {
public:
static std::unique_ptr<LuaVerifier> Create(
const std::string& context, const std::string& verifier_code,
const UniLib::RegexMatcher* matcher);
bool Verify(bool* result);
private:
explicit LuaVerifier(const std::string& context,
const std::string& verifier_code,
const UniLib::RegexMatcher* matcher)
: context_(context), verifier_code_(verifier_code), matcher_(matcher) {}
bool Initialize();
// Provides details of a capturing group to lua.
int GetCapturingGroup();
const std::string& context_;
const std::string& verifier_code_;
const UniLib::RegexMatcher* matcher_;
};
bool LuaVerifier::Initialize() {
// Run protected to not lua panic in case of setup failure.
return RunProtected([this] {
LoadDefaultLibraries();
// Expose context of the match as `context` global variable.
PushString(context_);
lua_setglobal(state_, "context");
// Expose match array as `match` global variable.
// Each entry `match[i]` exposes the ith capturing group as:
// * `begin`: span start
// * `end`: span end
// * `text`: the text
BindTable<LuaVerifier, &LuaVerifier::GetCapturingGroup>("match");
lua_setglobal(state_, "match");
return LUA_OK;
}) == LUA_OK;
}
std::unique_ptr<LuaVerifier> LuaVerifier::Create(
const std::string& context, const std::string& verifier_code,
const UniLib::RegexMatcher* matcher) {
auto verifier = std::unique_ptr<LuaVerifier>(
new LuaVerifier(context, verifier_code, matcher));
if (!verifier->Initialize()) {
TC3_LOG(ERROR) << "Could not initialize lua environment.";
return nullptr;
}
return verifier;
}
int LuaVerifier::GetCapturingGroup() {
if (lua_type(state_, /*idx=*/-1) != LUA_TNUMBER) {
TC3_LOG(ERROR) << "Unexpected type for match group lookup: "
<< lua_type(state_, /*idx=*/-1);
lua_error(state_);
return 0;
}
const int group_id = static_cast<int>(lua_tonumber(state_, /*idx=*/-1));
int status = UniLib::RegexMatcher::kNoError;
const CodepointSpan span = {matcher_->Start(group_id, &status),
matcher_->End(group_id, &status)};
std::string text = matcher_->Group(group_id, &status).ToUTF8String();
if (status != UniLib::RegexMatcher::kNoError) {
TC3_LOG(ERROR) << "Could not extract span from capturing group.";
lua_error(state_);
return 0;
}
lua_newtable(state_);
lua_pushinteger(state_, span.first);
lua_setfield(state_, /*idx=*/-2, "begin");
lua_pushinteger(state_, span.second);
lua_setfield(state_, /*idx=*/-2, "end");
PushString(text);
lua_setfield(state_, /*idx=*/-2, "text");
return 1;
}
bool LuaVerifier::Verify(bool* result) {
if (luaL_loadbuffer(state_, verifier_code_.data(), verifier_code_.size(),
/*name=*/nullptr) != LUA_OK) {
TC3_LOG(ERROR) << "Could not load verifier snippet.";
return false;
}
if (lua_pcall(state_, /*nargs=*/0, /*nresults=*/1, /*errfunc=*/0) != LUA_OK) {
TC3_LOG(ERROR) << "Could not run verifier snippet.";
return false;
}
if (RunProtected(
[this, result] {
if (lua_type(state_, /*idx=*/-1) != LUA_TBOOLEAN) {
TC3_LOG(ERROR) << "Unexpected verification result type: "
<< lua_type(state_, /*idx=*/-1);
lua_error(state_);
return LUA_ERRRUN;
}
*result = lua_toboolean(state_, /*idx=*/-1);
return LUA_OK;
},
/*num_args=*/1) != LUA_OK) {
TC3_LOG(ERROR) << "Could not read lua result.";
return false;
}
return true;
}
} // namespace
bool SetFieldFromCapturingGroup(const int group_id,
const FlatbufferFieldPath* field_path,
const UniLib::RegexMatcher* matcher,
ReflectiveFlatbuffer* flatbuffer) {
int status = UniLib::RegexMatcher::kNoError;
std::string group_text = matcher->Group(group_id, &status).ToUTF8String();
if (status != UniLib::RegexMatcher::kNoError || group_text.empty()) {
return false;
}
return flatbuffer->ParseAndSet(field_path, group_text);
}
bool VerifyMatch(const std::string& context,
const UniLib::RegexMatcher* matcher,
const std::string& lua_verifier_code) {
bool status = false;
auto verifier = LuaVerifier::Create(context, lua_verifier_code, matcher);
if (verifier == nullptr) {
TC3_LOG(ERROR) << "Could not create verifier.";
return false;
}
if (!verifier->Verify(&status)) {
TC3_LOG(ERROR) << "Could not create verifier.";
return false;
}
return status;
}
} // namespace libtextclassifier3