Skip to content
This repository has been archived by the owner on Sep 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #565 from Jaseci-Labs/thakee-access-pass
Browse files Browse the repository at this point in the history
access check for symbols implemented
  • Loading branch information
marsninja authored Aug 29, 2024
2 parents 39cc924 + 6daf24a commit 242ebc9
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 200 deletions.
231 changes: 85 additions & 146 deletions jaclang/compiler/passes/main/access_modifier_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,40 @@
This pass checks for access to attributes in the Jac language.
"""

import os
from typing import Optional

import jaclang.compiler.absyntree as ast
from jaclang.compiler.constant import SymbolAccess
from jaclang.compiler.passes import Pass
from jaclang.compiler.symtable import SymbolTable
from jaclang.compiler.symtable import Symbol
from jaclang.settings import settings


class AccessCheckPass(Pass):
"""Jac Ast Access Check pass."""

def after_pass(self) -> None:
"""After pass."""
pass
# NOTE: This method is a hacky way to detect if the drivied class is inherit from base class, it
# doesn't work if the base class was provided as an expression (ex. obj Dri :module.Base: {...}).
def is_class_inherited_from(
self, dri_class: ast.Architype, base_class: ast.Architype
) -> bool:
"""Return true if the dri_class inherited from base_class."""
if dri_class.base_classes is None:
return False
for expr in dri_class.base_classes.items:
if not isinstance(expr, ast.Name):
continue
if not isinstance(expr.name_of, ast.Architype):
continue # Unlikely.
if expr.name_of == base_class:
return True
if self.is_class_inherited_from(expr.name_of, base_class):
return True
return False

def report_error(self, message: str, node: Optional[ast.AstNode] = None) -> None:
"""Report error message related to illegal access of attributes and objects."""
self.error(message, node)

def exit_node(self, node: ast.AstNode) -> None: # TODO: Move to debug pass
"""Exit node."""
Expand All @@ -36,140 +54,6 @@ def exit_node(self, node: ast.AstNode) -> None: # TODO: Move to debug pass
):
self.warning(f"Name {node.sym_name} not present in symbol table")

def access_check(self, node: ast.Name) -> None:
"""Access check."""
node_info = (
node.sym_tab.lookup(node.sym_name)
if isinstance(node.sym_tab, SymbolTable)
else None
)

if node.sym:
decl_package_path = os.path.dirname(
os.path.abspath(node.sym.defn[-1].loc.mod_path)
)
use_package_path = os.path.dirname(os.path.abspath(node.loc.mod_path))
else:
decl_package_path = use_package_path = ""

if (
node_info
and node.sym
and node_info.access == SymbolAccess.PROTECTED
and decl_package_path != use_package_path
):
return self.error(
f'Can not access protected variable "{node.sym_name}" from {decl_package_path}'
f" to {use_package_path}."
)

if (
node_info
and node.sym
and node_info.access == SymbolAccess.PRIVATE
and node.sym.defn[-1].loc.mod_path != node.loc.mod_path
):
return self.error(
f'Can not access private variable "{node.sym_name}" from {node.sym.defn[-1].loc.mod_path}'
f" to {node.loc.mod_path}."
)

def access_register(
self, node: ast.AstSymbolNode, acc_tag: Optional[SymbolAccess] = None
) -> None:
"""Access register."""

def enter_global_vars(self, node: ast.GlobalVars) -> None:
"""Sub objects.
access: Optional[SubTag[Token]],
assignments: SubNodeList[Assignment],
is_frozen: bool,
"""
pass

def enter_module(self, node: ast.Module) -> None:
"""Sub objects.
name: str,
doc: Token,
body: Optional['Elements'],
mod_path: str,
is_imported: bool,
"""

def enter_architype(self, node: ast.Architype) -> None:
"""Sub objects.
name: Name,
arch_type: Token,
access: Optional[SubTag[Token]],
base_classes: Optional[SubNodeList[Expr]],
body: Optional[SubNodeList[ArchBlockStmt] | ArchDef],
decorators: Optional[SubNodeList[Expr]] = None,
"""
pass

def enter_enum(self, node: ast.Enum) -> None:
"""Sub objects.
name: Name,
access: Optional[SubTag[Token]],
base_classes: Optional[SubNodeList[Expr]],
body: Optional[SubNodeList[EnumBlockStmt] | EnumDef],
decorators: Optional[SubNodeList[Expr]] = None,
"""
pass

def enter_ability(self, node: ast.Ability) -> None:
"""Sub objects.
name_ref: NameSpec,
is_func: bool,
is_async: bool,
is_override: bool,
is_static: bool,
is_abstract: bool,
access: Optional[SubTag[Token]],
signature: Optional[FuncSignature | EventSignature],
body: Optional[SubNodeList[CodeBlockStmt] | AbilityDef],
decorators: Optional[SubNodeList[Expr]] = None,
"""
pass

def enter_sub_node_list(self, node: ast.SubNodeList) -> None:
"""Sub objects.
items: list[T]
"""

def enter_arch_has(self, node: ast.ArchHas) -> None:
"""Sub objects.
is_static: bool,
access: Optional[SubTag[Token]],
vars: SubNodeList[HasVar],
is_frozen: bool,
"""
pass

def enter_atom_trailer(self, node: ast.AtomTrailer) -> None:
"""Sub objects.
access: Optional[SubTag[Token]],
"""
pass

def enter_func_call(self, node: ast.FuncCall) -> None:
"""Sub objects.
target: Expr,
params: Optional[SubNodeList[Expr | KWPair]],
genai_call: Optional[FuncCall],
kid: Sequence[AstNode],
"""
pass

def enter_name(self, node: ast.Name) -> None:
"""Sub objects.
Expand All @@ -180,12 +64,67 @@ def enter_name(self, node: ast.Name) -> None:
pos_start: int,
pos_end: int,
"""
from jaclang.compiler.passes import Pass
# TODO: Enums are not considered at the moment, I'll need to test and add them bellow.

if isinstance(node.parent, ast.FuncCall):
self.access_check(node)
# If the current node is a global variable's name there is no access, it's just the declaration.
if Pass.find_parent_of_type(node, ast.GlobalVars) is not None:
return

if node.sym and Pass.find_parent_of_type(
node=node.sym.defn[-1], typ=ast.GlobalVars
):
self.access_check(node)
# Class name, and ability name are declarations and there is no access here as well.
if isinstance(node.name_of, (ast.Ability, ast.Architype, ast.Enum)):
return

# Get the context to check the access.
curr_class: Optional[ast.Architype] = Pass.find_parent_of_type(
node, ast.Architype
)
curr_module: Optional[ast.Module] = Pass.find_parent_of_type(node, ast.Module)
if curr_module is None:
return

# Note that currently we can only check for name + symbols, because expressions are not associated with the
# typeinfo thus they don't have a symbol. In the future the name nodes will become expression nodes.
if not isinstance(node.sym, Symbol):
return

# Public symbols are fine.
if node.sym.access == SymbolAccess.PUBLIC:
return

# Note that from bellow the access is either private or protected.
is_portect = node.sym.access == SymbolAccess.PROTECTED
access_type = "protected" if is_portect else "private"

# The class we're currently in (None if we're not inside any).
sym_owner: ast.AstNode = node.sym.parent_tab.owner

# If the symbol belongs to a class, we need to check if the access used properly
# within the class and in it's inherited classes.
if isinstance(sym_owner, ast.Architype):

# Accessing a private/protected member within the top level scope illegal.
if curr_class is None:
return self.report_error(
f'Error: Invalid access of {access_type} member "{node.sym_name}".',
node,
)

if curr_class != node.sym.parent_tab.owner:
if not is_portect: # private member accessed in a different class.
return self.report_error(
f'Error: Invalid access of {access_type} member "{node.sym_name}".',
node,
)
else: # Accessing a protected member, check we're in an inherited class.
if not self.is_class_inherited_from(curr_class, sym_owner):
return self.report_error(
f'Error: Invalid access of {access_type} member "{node.sym_name}".',
node,
)

elif isinstance(sym_owner, ast.Module) and sym_owner != curr_module:
# Accessing a private/public member in a different module.
return self.report_error(
f'Error: Invalid access of {access_type} member "{node.sym_name}".',
node,
)
30 changes: 30 additions & 0 deletions jaclang/compiler/passes/main/fuse_typeinfo_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ def __set_sym_table_link(self, node: ast.AstSymbolNode) -> None:
if typ[0] == "builtins":
return

if node.sym_type == "types.ModuleType" and node.sym:
node.name_spec.type_sym_tab = node.sym.parent_tab.find_scope(node.sym_name)

assert isinstance(self.ir, ast.Module)

if typ_sym_table:
Expand Down Expand Up @@ -489,3 +492,30 @@ def exit_atom_trailer(self, node: ast.AtomTrailer) -> None:
right, ast.IndexSlice
): # TODO check why IndexSlice produce an issue
right.name_spec.sym = left.type_sym_tab.lookup(right.sym_name)

# # TODO [Gamal]: Need to support getting the type of an expression
# # NOTE: Note sure why we're inferring the symbol here instead of the sym table build pass
# # I afraid that moving this there might break something.
# def lookup_sym_from_node(self, node: ast.AstNode, member: str) -> Optional[Symbol]:
# """Recursively look for the symbol of a member from a given node."""
# if isinstance(node, ast.AstSymbolNode):
# if node.type_sym_tab is None:
# return None
# return node.type_sym_tab.lookup(member)

# if isinstance(node, ast.AtomTrailer):
# if node.is_attr: # <expr>.member access.
# return self.lookup_sym_from_node(node.right, member)
# elif isinstance(node.right, ast.IndexSlice):
# # NOTE: if the 'node.target' is a variable of type list[T] the
# # node.target.sym_type is "builtins.list[T]" string Not sure how
# # to get the type_sym_tab of "T" from just the name itself. would
# # be better if the symbols types are not just strings but references.
# # For now I'll mark them as todos.
# # TODO:
# # case 1: expr[i] -> regular indexing.
# # case 2: expr[i:j:k] -> returns a sublist.
# # case 3: expr["str"] -> dictionary lookup.
# pass

# return None
29 changes: 12 additions & 17 deletions jaclang/tests/fixtures/access_checker.jac
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
obj :priv BankAccount {
has account_no: int;
can register() {
print(f"{self.account_no} has been registered.");
}
}
can :priv privmethod() {
return "private method";
}
can :priv overide_check() {
return "inside access_check";
}
glob :priv b=1;
glob :priv p=0;

obj :pub ModulePublObj {}
obj :priv ModulePrivObj {}

glob :pub module_publ_glob:int = 0;
glob :priv module_priv_glob:int = 0;

with entry {
public = BankAccount(123);
public.register();
privmethod();
ModulePrivObj(); # <-- okey.
ModulePublObj(); # <-- okey.

module_publ_glob; # <-- okey.
module_priv_glob; # <-- okey.
}
Loading

0 comments on commit 242ebc9

Please sign in to comment.