* Copyright (c) 2021-2026 Huawei Device Co., Ltd.
* 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 "internalAPICheck.h"
#include "checker/types/signature.h"
#include "checker/types/ets/etsObjectType.h"
#include "ir/base/classDefinition.h"
#include "ir/base/classProperty.h"
#include "ir/base/methodDefinition.h"
#include "ir/base/scriptFunction.h"
#include "ir/expressions/identifier.h"
#include "ir/expressions/memberExpression.h"
#include "ir/ets/etsTypeReference.h"
#include "ir/ets/etsNewClassInstanceExpression.h"
#include "ir/statements/annotationDeclaration.h"
#include "ir/statements/annotationUsage.h"
#include "ir/statements/functionDeclaration.h"
#include "ir/statements/variableDeclaration.h"
#include "ir/statements/variableDeclarator.h"
#include "ir/ts/tsInterfaceDeclaration.h"
namespace ark::es2panda::compiler {
constexpr std::string_view ACCESS_RESTRICTION_ANNOTATION = "arkruntime.annotation.AccessRestriction";
constexpr std::string_view ACCESS_RESTRICTION_MODULES = "modules";
struct RestrictionInfo {
std::string annotationName;
std::vector<std::string> modules;
};
using RestrictionCache = std::unordered_map<ir::AnnotationDeclaration const *, std::optional<RestrictionInfo>>;
static bool NamespaceIsPrefixedWith(std::string_view internalName, std::string_view prefix)
{
return internalName.rfind(prefix, 0) == 0 &&
(internalName.length() == prefix.length() || internalName[prefix.length()] == '.');
}
static const ArenaVector<ir::AnnotationUsage *> *GetAnnotations(ir::AstNode const *declNode)
{
if (declNode->IsClassDefinition()) {
return &declNode->AsClassDefinition()->Annotations();
}
if (declNode->IsTSInterfaceDeclaration()) {
return &declNode->AsTSInterfaceDeclaration()->Annotations();
}
if (declNode->IsAnnotationDeclaration()) {
return &declNode->AsAnnotationDeclaration()->Annotations();
}
if (declNode->IsFunctionDeclaration()) {
return &declNode->AsFunctionDeclaration()->Annotations();
}
if (declNode->IsVariableDeclaration()) {
return &declNode->AsVariableDeclaration()->Annotations();
}
if (declNode->IsClassProperty()) {
return &declNode->AsClassProperty()->Annotations();
}
if (declNode->IsScriptFunction()) {
return &declNode->AsScriptFunction()->Annotations();
}
return nullptr;
}
static const ir::AnnotationDeclaration *ResolveAnnotationDeclaration(ir::AnnotationUsage const *anno)
{
auto *baseName = anno->GetBaseName();
if (baseName == nullptr) {
return nullptr;
}
auto *baseVar = baseName->Variable();
if (baseVar == nullptr) {
auto *qualifiedName = baseName->Parent() != nullptr && baseName->Parent()->IsTSQualifiedName()
? baseName->Parent()->AsTSQualifiedName()
: nullptr;
if (qualifiedName == nullptr) {
return nullptr;
}
auto *left = qualifiedName->Left();
while (left != nullptr && left->IsTSQualifiedName()) {
left = left->AsTSQualifiedName()->Left();
}
if (left == nullptr || !left->IsIdentifier()) {
return nullptr;
}
auto *leftVar = left->AsIdentifier()->Variable();
if (leftVar == nullptr || leftVar->TsType() == nullptr || !leftVar->TsType()->IsETSObjectType()) {
return nullptr;
}
baseVar = leftVar->TsType()->AsETSObjectType()->GetProperty(baseName->Name(),
checker::PropertySearchFlags::SEARCH_DECL);
if (baseVar == nullptr) {
return nullptr;
}
}
auto *declNode = baseVar->Declaration() != nullptr ? baseVar->Declaration()->Node() : nullptr;
if (declNode == nullptr || !declNode->IsAnnotationDeclaration()) {
return nullptr;
}
return declNode->AsAnnotationDeclaration();
}
static bool IsAccessRestrictionAnnotation(ir::AnnotationDeclaration const *annoDecl)
{
return annoDecl != nullptr && annoDecl->InternalName().Is(ACCESS_RESTRICTION_ANNOTATION);
}
static void CollectModulesFromValue(ir::Expression const *value, std::vector<std::string> &modules)
{
if (value == nullptr || !value->IsArrayExpression()) {
return;
}
for (auto *element : value->AsArrayExpression()->Elements()) {
if (element != nullptr && element->IsStringLiteral()) {
modules.emplace_back(element->AsStringLiteral()->Str().Mutf8());
}
}
}
static std::optional<RestrictionInfo> ParseRestrictionInfo(ir::AnnotationDeclaration const *declNode)
{
auto const *annotations = GetAnnotations(declNode);
if (annotations == nullptr) {
return std::nullopt;
}
for (auto *anno : *annotations) {
auto *restrictionDecl = ResolveAnnotationDeclaration(anno);
if (!IsAccessRestrictionAnnotation(restrictionDecl)) {
continue;
}
RestrictionInfo info {declNode->GetBaseName()->Name().Mutf8(), {}};
for (auto *propNode : anno->Properties()) {
auto *prop = propNode->AsClassProperty();
if (prop == nullptr || prop->Id() == nullptr) {
continue;
}
auto const propName = prop->Id()->Name();
if (!propName.Is(ACCESS_RESTRICTION_MODULES) && propName != compiler::Signatures::ANNOTATION_KEY_VALUE) {
continue;
}
CollectModulesFromValue(prop->Value(), info.modules);
}
return info.modules.empty() ? std::nullopt : std::optional<RestrictionInfo> {std::move(info)};
}
return std::nullopt;
}
static RestrictionInfo const *GetRestrictionInfo(ir::AnnotationDeclaration const *annoDecl, RestrictionCache &cache)
{
auto const [it, inserted] = cache.emplace(annoDecl, std::nullopt);
if (inserted) {
it->second = ParseRestrictionInfo(annoDecl);
}
return it->second.has_value() ? &it->second.value() : nullptr;
}
static RestrictionInfo const *GetRestrictionInfo(ir::AstNode const *declNode, RestrictionCache &cache)
{
auto const *annotations = GetAnnotations(declNode);
if (annotations == nullptr) {
return nullptr;
}
for (auto *anno : *annotations) {
auto *annoDecl = ResolveAnnotationDeclaration(anno);
if (annoDecl == nullptr) {
continue;
}
auto *info = GetRestrictionInfo(annoDecl, cache);
if (info != nullptr) {
return info;
}
}
return nullptr;
}
static bool IsAccessibleFromModule(RestrictionInfo const &info, std::string_view moduleName)
{
return std::any_of(info.modules.begin(), info.modules.end(),
[moduleName](std::string const &prefix) { return NamespaceIsPrefixedWith(moduleName, prefix); });
}
static RestrictionInfo const *GetAppliedRestriction(ir::AstNode const *declNode, ir::AstNode const *useSite,
std::string_view moduleName, RestrictionCache &cache)
{
auto *info = GetRestrictionInfo(declNode, cache);
if (info == nullptr) {
return nullptr;
}
auto *declProgram = declNode->Program();
auto *useProgram = useSite != nullptr ? useSite->Program() : nullptr;
if (declProgram != nullptr && useProgram != nullptr && declProgram == useProgram &&
declNode->Start().Program() == useSite->Start().Program()) {
return nullptr;
}
return IsAccessibleFromModule(*info, moduleName) ? nullptr : info;
}
static lexer::SourcePosition GetReportPosition(ir::AstNode const *useSite)
{
if (useSite->IsETSNewClassInstanceExpression()) {
auto *typeRef = useSite->AsETSNewClassInstanceExpression()->GetTypeRef();
if (typeRef != nullptr) {
return typeRef->Start();
}
}
return useSite->Start();
}
static std::string GetRestrictedEntityName(ir::AstNode const *declNode)
{
if (declNode->IsAnnotationDeclaration()) {
return declNode->AsAnnotationDeclaration()->GetBaseName()->Name().Mutf8();
}
if (declNode->IsClassDefinition()) {
return declNode->AsClassDefinition()->Ident()->Name().Mutf8();
}
if (declNode->IsTSInterfaceDeclaration()) {
return declNode->AsTSInterfaceDeclaration()->Id()->Name().Mutf8();
}
if (declNode->IsClassProperty() && declNode->AsClassProperty()->Id() != nullptr) {
return declNode->AsClassProperty()->Id()->Name().Mutf8();
}
if (declNode->IsVariableDeclaration()) {
auto const &declarators = declNode->AsVariableDeclaration()->Declarators();
if (!declarators.empty() && declarators[0]->Id() != nullptr && declarators[0]->Id()->IsIdentifier()) {
return declarators[0]->Id()->AsIdentifier()->Name().Mutf8();
}
}
if (declNode->IsScriptFunction()) {
auto *scriptFunc = declNode->AsScriptFunction();
if (scriptFunc->IsConstructor()) {
return std::string {compiler::Signatures::CONSTRUCTOR_NAME};
}
if (scriptFunc->Id() != nullptr) {
return scriptFunc->Id()->Name().Mutf8();
}
auto *method = scriptFunc->Parent() != nullptr && scriptFunc->Parent()->IsMethodDefinition()
? scriptFunc->Parent()->AsMethodDefinition()
: nullptr;
if (method != nullptr && method->Id() != nullptr && method->Id()->IsIdentifier()) {
return method->Id()->AsIdentifier()->Name().Mutf8();
}
}
return std::string {};
}
static void LogRestrictedUse(checker::ETSChecker *checker, ir::AstNode const *useSite, ir::AstNode const *declNode,
RestrictionInfo const &info)
{
checker->LogError(diagnostic::ARKRUNTIME_INTERNAL_API_ACCESS,
{GetRestrictedEntityName(declNode), info.annotationName}, GetReportPosition(useSite));
}
static bool IsDeclarationNameReference(ir::AstNode const *node)
{
auto *parent = node->Parent();
if (parent == nullptr) {
return false;
}
if (parent->IsClassDefinition()) {
return parent->AsClassDefinition()->Ident() == node;
}
if (parent->IsAnnotationDeclaration()) {
return parent->AsAnnotationDeclaration()->GetBaseName() == node;
}
if (parent->IsFunctionDeclaration()) {
return parent->AsFunctionDeclaration()->Function() != nullptr &&
parent->AsFunctionDeclaration()->Function()->Id() == node;
}
if (parent->IsClassProperty()) {
return parent->AsClassProperty()->Id() == node;
}
if (parent->IsVariableDeclarator()) {
return parent->AsVariableDeclarator()->Id() == node;
}
if (parent->IsMethodDefinition()) {
return parent->AsMethodDefinition()->Id() == node;
}
if (parent->IsTSInterfaceDeclaration()) {
return parent->AsTSInterfaceDeclaration()->Id() == node;
}
if (parent->IsScriptFunction()) {
return parent->AsScriptFunction()->Id() == node;
}
return false;
}
static void CheckTypeReference(checker::ETSChecker *checker, std::string_view moduleName, ir::AstNode const *node,
checker::Type const *type, RestrictionCache &cache)
{
if (type == nullptr || IsDeclarationNameReference(node)) {
return;
}
if (node->Parent()->IsAnnotationUsage()) {
auto *declNode = ResolveAnnotationDeclaration(node->Parent()->AsAnnotationUsage());
auto *info = declNode != nullptr ? GetAppliedRestriction(declNode, node, moduleName, cache) : nullptr;
if (info != nullptr) {
LogRestrictedUse(checker, node, declNode, *info);
}
return;
}
if (type->IsETSObjectType()) {
auto *declNode = type->AsETSObjectType()->GetDeclNode();
auto *info = declNode != nullptr ? GetAppliedRestriction(declNode, node, moduleName, cache) : nullptr;
if (info != nullptr) {
LogRestrictedUse(checker, node, declNode, *info);
}
}
}
static void CheckResolvedVariable(checker::ETSChecker *checker, std::string_view moduleName, ir::AstNode const *useSite,
varbinder::Variable const *var, RestrictionCache &cache)
{
if (var == nullptr || var->Declaration() == nullptr || IsDeclarationNameReference(useSite)) {
return;
}
auto *parent = useSite->Parent();
if (parent != nullptr && parent->IsMemberExpression() && parent->AsMemberExpression()->Property() == useSite) {
return;
}
if (parent != nullptr && parent->IsAnnotationUsage() && parent->AsAnnotationUsage()->GetBaseName() == useSite) {
return;
}
auto *declNode = var->Declaration()->Node();
auto *info = declNode != nullptr ? GetAppliedRestriction(declNode, useSite, moduleName, cache) : nullptr;
if (info != nullptr) {
LogRestrictedUse(checker, useSite, declNode, *info);
}
}
static void CheckResolvedSignature(checker::ETSChecker *checker, std::string_view moduleName,
ir::AstNode const *useSite, checker::Signature const *signature,
RestrictionCache &cache)
{
if (signature == nullptr || !signature->HasFunction()) {
return;
}
auto *funcDecl = signature->Function();
auto *info = GetAppliedRestriction(funcDecl, useSite, moduleName, cache);
if (info != nullptr) {
LogRestrictedUse(checker, useSite, funcDecl, *info);
}
}
static void EnforceChecks(public_lib::Context *ctx, parser::Program *program)
{
auto *checker = ctx->GetChecker()->AsETSChecker();
auto moduleName = std::string {program->ModuleName()};
if (moduleName.empty()) {
moduleName = program->RelativeFilePath(ctx);
}
RestrictionCache cache;
program->Ast()->IterateRecursively([checker, moduleName, &cache](ir::AstNode *node) {
if (node->IsIdentifier()) {
auto *ident = node->AsIdentifier();
if (ident->Variable() != nullptr) {
CheckResolvedVariable(checker, moduleName, node, ident->Variable(), cache);
CheckTypeReference(checker, moduleName, node, ident->Variable()->TsType(), cache);
}
} else if (node->IsETSTypeReference()) {
CheckTypeReference(checker, moduleName, node, node->AsETSTypeReference()->TsType(), cache);
} else if (node->IsMemberExpression()) {
auto *memberExpr = node->AsMemberExpression();
if (memberExpr->PropVar() != nullptr) {
CheckResolvedVariable(checker, moduleName, node, memberExpr->PropVar(), cache);
}
} else if (node->IsCallExpression()) {
CheckResolvedSignature(checker, moduleName, node, node->AsCallExpression()->Signature(), cache);
} else if (node->IsETSNewClassInstanceExpression()) {
CheckResolvedSignature(checker, moduleName, node, node->AsETSNewClassInstanceExpression()->Signature(),
cache);
}
});
}
bool InternalAPICheck::PerformForProgram(parser::Program *program)
{
EnforceChecks(Context(), program);
return true;
}
}