@lcap/nasl-parser
Version:
Take Nasl text to Nasl AST with the help of generalized parsing.
555 lines (481 loc) • 18 kB
text/typescript
import * as nasl from '@lcap/nasl';
import { isLogicDeclaration, isIdentifier, isCallLogic, isLogic, isAnonymousFunction, isParam, isVariable, isCallQueryComponent, isTypeAnnotation, isStructure, isEntity, isMemberExpression } from '@lcap/nasl-concepts/asserts';
import { CstIdentifier, CstIntEnumRef, UsingNamespace } from './decorated-nasl-ast';
import {
head, last, isEmpty, projectIdentifierAST, NASL_CORE_NL, wellDefined, SimpleSillyDeepClone,
NAMESPACE_SEP_AST,
NASL_UTIL_AST,
projectIdentifier,
APP_LOGICS_AST,
removeDup,
APP_STRUCTURE_AST,
APP_ENUM,
prependNamespace,
createQIdentifier,
splitByPredicate,
NAMESPACE_SEP,
APP_ENTITY_AST,
extractIdent,
NASL_LOGGING_AST,
NASL_COLLECTION_AST,
} from './common-util';
import { builtinFunctionNames } from './builtin-namespace';
import { addConstructor, addEnumCons, addRefTypeSemInfo, addResolvedNode, CompilerEnvInfo, NodeSemanticInfo } from './resolve-global-bindings';
// 本质上可以合并为一个函数,但是 NASL 上的节点类型不同,所以分开处理了
/*
【P0】调用方式 1:直接带 namespace,
nasl::util::方法名(...) 内置函数,硬编码
[O] 内置函数方法名(...) 内置函数,硬编码
app::logics::方法名(...) 逻辑调用,硬编码
[O] My::Namespace::方法名(...) 逻辑调用
[O] 方法名(...) 逻辑调用,无 namespace 默认
apis::应用id::interfaces::方法名(...) 接口调用
connector::连接器种类::连接器名(操作, 数据, ...) 连接器
【P1】调用方式 2:打开 namespace 后直接调用
[O] using nasl.util
[O] using app.logics
using apis.应用id.interfaces
using connector.连接器种类
特殊调用
CallQueryComponent(...) //数据查询
OqlQueryComponent(dataSource, code) // Oql SQL 查询
JsonSerialize()
JsonDeserialize()
*/
export function resolveCallee(cei: CompilerEnvInfo, callee: nasl.CallLogic):
nasl.CallLogic | nasl.CallFunction | nasl.NewComposite | nasl.NewStructure {
if (!isCallLogic(callee)) {
return callee;
}
const _qName = callee.qName;
// 优先找 constructor,在下一个 pass 翻译到 new composite
{
let test = false;
if (lookUpResolvedNodes(cei, { namespace: _qName.namespace, name:`${_qName.name}.constructor` }).length === 1) {
test = true;
}
if (!test) {
createHardCodedRefTypeOnUse(cei, _qName);
test = lookUpResolvedNodes(cei, { namespace: _qName.namespace, name:`${_qName.name}.constructor` }).length === 1;
}
if (test) {
callee.callKind = 'Constructor';
callee.name = _qName.name;
return ctorToNewExpr(cei, callee);
}
}
{
// builtin functions 内置函数
if (builtinFunctionNames.includes(_qName.name)) {
callee.callKind = 'Function';
callee.calleeNamespace = NASL_UTIL_AST;
return toNaslCallFunction(callee);
}
}
{
if (_qName.name === 'jsonSerialize') {
callee.calleeName = 'jsonSerialize';
callee.calleeNamespace = NASL_UTIL_AST;
callee.label = 'JSON 序列化';
return callee;
}
}
{
if (_qName.name === 'jsonDeserialize') {
callee.calleeName = 'jsonDeserialize';
callee.calleeNamespace = NASL_UTIL_AST;
callee.label = 'JSON 反序列化';
return callee;
}
}
//
{
if (['INFO', 'ERROR', 'WARN', 'DEBUG'].includes (_qName.name)) {
callee.calleeName = _qName.name;
callee.calleeNamespace = NASL_LOGGING_AST;
callee.label = '输出日志';
return callee;
}
}
// general cases
// @ts-ignore
const defs = lookUpResolvedNodes(cei, callee.qName);
if (defs.length === 1) {
const nd = head(defs);
callee.calleeNamespace = APP_LOGICS_AST;
// callee.name = nd.name; 好像不需要
// const info = cei.semInfo.get(nd);
return callee;
} else if (defs.length > 1) {
let msg = `Ambiguous name: ${projectIdentifier(_qName)}, it could be:\n` +
defs.map(n => (n as nasl.Identifier).name).join('\n');
throw new Error(msg);
} else {
const msg = `In resolveCallee, Unresolved identifier: ${projectIdentifier(_qName)}`;
throw new Error(msg);
}
}
export function resolveQEnumId(cei: CompilerEnvInfo, id: nasl.Identifier | CstIntEnumRef): nasl.Identifier | nasl.MemberExpression | CstIntEnumRef {
if (!(isIdentifier(id) && id?.qName) && !(id instanceof CstIntEnumRef)) {
// Identifier 变为 MemberExpression 后,子节点有个 Identifier,不需要再设置 qName 了,所以没有 qName
return id;
}
// hard-coded and non-hard-coded int enum app::enums::EnumName::123
if (id instanceof CstIntEnumRef) {
return new nasl.MemberExpression({
object: new nasl.Identifier({
namespace: APP_ENUM,
name: id.enumTypeName,
}),
property: new nasl.Identifier({
name: id.value,
}),
});
}
const _qName = id.qName;
// hard-coded string enum
if (/app.enums./.test(_qName.namespace.join(NAMESPACE_SEP_AST))) {
return createEnumIdMemberExpr();
}
// non-hard-coded string enum
const def = lookUpResolvedNodes(cei, _qName)?.[0] as nasl.EnumItem;
if (def?.concept === 'EnumItem') {
return createEnumIdMemberExpr();
}
// otherwise, do nothing
return id;
function createEnumIdMemberExpr() {
return new nasl.MemberExpression({
object: new nasl.Identifier({
namespace: APP_ENUM,
name: last(_qName.namespace),
}),
property: new nasl.Identifier({
name: _qName.name
}),
});
}
}
function resolveBuiltinCollectionType(ty: nasl.TypeAnnotation): nasl.TypeAnnotation {
const ngt = new nasl.TypeAnnotation({
typeKind: 'generic',
typeName: ty.qName.name,
typeNamespace: NASL_COLLECTION_AST,
typeArguments: ty.typeArguments,
});
return ngt;
}
export function resolveTypeRef(cei: CompilerEnvInfo, ty: nasl.TypeAnnotation): nasl.TypeAnnotation {
if (!ty || !isTypeAnnotation(ty) || !ty.qName) {
return ty;
}
if (ty.typeKind === 'reference') {
if (/^(List|Map)$/.test(ty.qName.name) &&
((ty.qName.namespace.length === 0) || new RegExp(NASL_COLLECTION_AST).test(ty.qName.namespace.join(NAMESPACE_SEP_AST)))) {
return resolveBuiltinCollectionType(ty);
}
let def = lookUpResolvedNodes(cei, ty.qName)?.[0] ?? resolveHardCodedRefType(cei, ty);
if (!def) {
throw new Error(`Unresolved type reference: ${projectIdentifier(ty.qName)}`);
}
const nsi = cei.semInfo.get(def);
ty.typeNamespace = nsi.namespace.join(NAMESPACE_SEP_AST);
ty.typeName = nsi.name;
}
return ty;
}
// 顺便返回一个(可能是虚假的)数据定义(语义需要),一期硬编码
function resolveHardCodedRefType(cei: CompilerEnvInfo, ty: nasl.TypeAnnotation): nasl.Entity | nasl.Structure | nasl.Enum {
if (ty.typeKind === 'reference') {
const nd = createHardCodedRefTypeOnUse(cei, ty.qName);
if (nd) {
// set proper type namespace and name
ty.typeNamespace = ty.qName.namespace.join(NAMESPACE_SEP_AST);
ty.typeName = ty.qName.name;
ty.qName = ty.qName
// if (!cei.semInfo.has(es)) {
// cei.semInfo.set(es, { name: ty.qName.name, namespace: ty.qName.namespace, isConst: false} );
// cei.resolvedNodes.add(projectIdentifierAST(ty.qName), es);
// }
return nd;
}
}
return null;
}
// 无论是看到类型,还是构造器又或是其他,都要创建个假的数据定义,以便后续分析
function createHardCodedRefTypeOnUse(cei: CompilerEnvInfo, qName: CstIdentifier) {
const nsp = qName.namespace.join(NAMESPACE_SEP_AST);
let test = false;
let nd: nasl.Entity | nasl.Structure | nasl.Enum;
let tempNsp = cei.currNamespace;
// app.structures
if (new RegExp(APP_STRUCTURE_AST).test(nsp)) {
nd = new nasl.Structure({
name: qName.name,
});
test = true;
tempNsp = APP_STRUCTURE_AST.split(NAMESPACE_SEP_AST);
}
// app.dataSources.???.entities
else if (/app.dataSources/.test(nsp) && /entities/.test(nsp)) {
nd = new nasl.Entity({
name: qName.name,
});
test = true;
tempNsp = APP_ENTITY_AST.split(NAMESPACE_SEP_AST);
}
// app.enums
else if (/app.enums/.test(nsp)) {
nd = new nasl.Enum({
name: qName.name,
});
test = true;
tempNsp = APP_ENUM.split(NAMESPACE_SEP_AST);
}
if (!test) {
return null;
}
// add to global definitions
const savedNsp = cei.currNamespace;
cei.currNamespace = tempNsp;
if (nd instanceof nasl.Entity || nd instanceof nasl.Structure) {
addResolvedNode(cei, nd);
addRefTypeSemInfo(cei, nd);
addConstructor(cei, nd);
} else if (nd instanceof nasl.Enum) {
addResolvedNode(cei, nd);
addEnumCons(cei, nd);
}
cei.currNamespace = savedNsp;
return nd;
}
export function lookUpResolvedNodes(cei: CompilerEnvInfo, nd: { namespace: Array<string>, name: string }) {
const qName = nd.namespace.concat(nd.name).join(NAMESPACE_SEP_AST);
let defs = [cei.resolvedNodes.get(qName)];
// 基于当前作用域,从内到外,找一遍
// ['nasl', 'core', 'utils'] 查找 nasl 下的、nasl::core 下的、nasl::core::utils 下的
expandNamespaces(cei.currNamespace).forEach(nsp => {
const n = prependNamespace(nsp, nd);
return defs.push(cei.resolvedNodes.get(n));
});
// 所有额外引入的命名空间,从内到外,找一遍
// 与上面的不同,引了 nasl::core::utils,那么只查找 nasl::core::utils 下的;因此没有 expandNamespaces
cei.namespaceOpen.forEach(nsp => {
const n = prependNamespace(nsp, nd);
defs.push(cei.resolvedNodes.get(n));
});
defs = removeDup(defs.filter(wellDefined));
if (defs.length > 1) {
let msg = `Ambiguous name: ${projectIdentifierAST(nd)}, it could be:\n` +
defs.map(n => (n as nasl.Identifier).name).join('\n');
throw new Error(msg);
}
return defs;
}
function expandNamespaces(fragments: Array<string>) {
const nss = [];
for (let i = 0; i < fragments.length; i++) {
nss.push(fragments.slice(0, i + 1));
}
return nss;
}
// export const addLambdaLocalBindings = (cei: CompilerEnvInfo, naf: nasl.AnonymousFunction): nasl.AnonymousFunction => {
// if (!isAnonymousFunction(naf)) {
// return naf;
// }
// // env 里添加形参(类似局部变量)
// cei.localVars.push([])
// naf?.params?.forEach(addLocalBindings(cei));
// return naf;
// }
// Add function-level scoped local variables (including parameters) to our symbol table.
const addLocalBindings = (cei: CompilerEnvInfo) => <T extends nasl.Param | nasl.Variable>(node: T): T => {
const rNodes = cei.resolvedNodes;
rNodes.add(node.name, node);
const nsc: NodeSemanticInfo = {
namespace: [],
name: node.name,
isConst: false,
};
if (node.typeAnnotation) {
nsc['type'] = node.typeAnnotation;
}
if (node.defaultValue) {
nsc['value'] = node.defaultValue.expression;
}
// constant analysis later
cei.semInfo.set(node, nsc);
last(cei.localVars).push(node.name);
return node;
}
export const addLogicLocalBindings =
(cei: CompilerEnvInfo, nl: nasl.Logic | nasl.AnonymousFunction)
:nasl.Logic | nasl.AnonymousFunction => {
if (!isLogic(nl) && !isAnonymousFunction(nl)) {
return nl;
}
// Add into symbol table a function's return's semantic information.
const addReturnBinding = (cei: CompilerEnvInfo, nr: nasl.Return) : nasl.Return => {
const retQVarName = nr.name;
cei.resolvedNodes.add(retQVarName, nr);
const nsc: NodeSemanticInfo = {
namespace: [],
name: nr.name,
isConst: false,
};
if (nr.typeAnnotation) {
nsc['type'] = nr.typeAnnotation;
}
if (nr.defaultValue) {
nsc['value'] = nr.defaultValue.expression;
}
// const analysis later
cei.semInfo.set(nr, nsc);
last(cei.localVars).push(retQVarName);
return nr;
}
cei.localVars.push([]);
// env 里添加形参(类似局部变量)
nl?.params?.forEach(addLocalBindings(cei));
// AnonymousFunction 里没有 variables 和 returns
if (nl instanceof nasl.Logic) {
// env 里添加局部变量
nl?.variables?.forEach(addLocalBindings(cei));
// 添加函数返回类型信息等,return 表达式要用
if (nl.returns && !isEmpty(nl.returns)) {
addReturnBinding(cei, head(nl.returns));
}
}
return nl;
}
export function removeLogicLocalBindings<T extends nasl.Logic | nasl.AnonymousFunction>(cei: CompilerEnvInfo, node: T): T {
if (!isAnonymousFunction(node) && !isLogic(node)) {
return node;
}
const nthe = cei.resolvedNodes;
let recentList = cei.localVars.pop();
recentList?.forEach(v => {
nthe.remove(v);
cei.semInfo.delete(nthe.get(v));
});
return node;
}
// using xxx.yyy.zzz;
/*
namespaceOpen: [
['app', 'dataSources', 'defaultDS', 'entities'],
['nasl'],
...
]
after using namespace core
namespaceOpen: [
['app', 'dataSources', 'defaultDS', 'entities'],
['nasl'],
['nasl', 'core'],
...
]
after closing namespace core
namespaceOpen: [
['app', 'dataSources', 'defaultDS', 'entities'],
['nasl'],
...
]
*/
// 尚未支持局部打开命名空间
// 尚未支持关闭打开的命名空间
export const usingNamespace = (cei: CompilerEnvInfo, thisNsp: UsingNamespace): UsingNamespace => {
if (thisNsp instanceof UsingNamespace) {
cei.namespaceOpen.push(thisNsp.namespace);
}
return thisNsp;
}
// 构造函数转为 New 连线构造,难伺候
function ctorToNewExpr(cei: CompilerEnvInfo, nd: nasl.CallLogic): nasl.CallLogic | nasl.NewStructure | nasl.NewComposite {
if (!isCallLogic(nd)) {
return nd;
}
if (nd.callKind !== 'Constructor') {
return nd;
}
const keywords = nd.arguments.map(arg => (
console.assert(arg.keyword, 'Arguments in New Composite constructors should all be named'),
arg.keyword)
);
const selMems = nd.arguments.map(arg =>
new nasl.SelectMembers({
// ? identifier xyz : literal 1 true 'abc'
expression: isMemberExpression(arg.expression) ? extractIdent(arg.expression) : arg.expression,
members: isMemberExpression(arg.expression) ? [arg.expression] : []
}));
// 无能为力,认怂
const lines = nd.arguments.map((arg, idx) =>
new nasl.AssignmentLine({
leftIndex: [0, idx],
rightIndex: isMemberExpression(arg.expression) ? [idx, 1] : [idx],
})
);
return new nasl.NewComposite({
// name: nd.name, 不要名字 难伺候
properties: keywords.map(k => new nasl.Identifier({name: k})),
rights: selMems,
assignmentLines: lines,
typeAnnotation: createNaslRefType(cei, nd.name),
});
}
function createNaslRefType(cei: CompilerEnvInfo, name: string): nasl.TypeAnnotation {
const defs = lookUpResolvedNodes(cei, { name, namespace: []});
const def = defs[0] as nasl.Entity | nasl.Structure | nasl.Enum;
const ty = new nasl.TypeAnnotation({
typeKind: 'reference',
typeNamespace: dispatchTypeNamespace(def),
typeName: name,
});
return ty;
}
const dispatchTypeNamespace = (nd: nasl.Entity | nasl.Structure | nasl.Enum) => {
if (isStructure(nd)) {
return APP_STRUCTURE_AST;
}
if (isEntity(nd)) {
return APP_ENTITY_AST;
}
// if (isEnum(nd)) {
// return null;
// }
throw new Error(`Unimplemented in dispatchTypeNamespace: ${nd.toString()}`);
}
function toNaslCallFunction(nc: nasl.CallLogic): nasl.CallFunction {
return new nasl.CallFunction({
calleeName: nc.qName.name,
calleeNamespace: NASL_UTIL_AST,
arguments: nc.arguments,
typeArguments: nc.typeArguments ?? [] // 难伺候
});
}
// 二期
function toNaslCallInterface(nc: nasl.CallLogic): nasl.CallInterface {
// 已经从 cst 的 .name 转成了 nasl ast 的 .keyword
const [authArgs, otherArgs] = splitByPredicate(nc.arguments, arg => arg.keyword === 'authArguments');
if (!isEmpty(authArgs)) {
return new nasl.CallAuthInterface({
calleeName: nc.qName.name,
calleeNamespace: nc.qName.namespace.join(NAMESPACE_SEP),
arguments: otherArgs,
authArguments: authArgs
});
} else {
return new nasl.CallInterface({
calleeName: nc.qName.name,
calleeNamespace: nc.qName.namespace.join(NAMESPACE_SEP),
arguments: nc.arguments
});
}
}
// 二期
function toNaslCallConnector(nc: nasl.CallLogic): nasl.CallConnector {
return new nasl.CallConnector({
calleeName: nc.qName.name,
calleeNamespace: nc.qName.namespace.join(NAMESPACE_SEP),
arguments: nc.arguments
});
}