From cffe38d7e9de3d32e3c390332139c46182c8bbc1 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Thu, 8 Feb 2024 03:56:20 +0900 Subject: [PATCH 01/11] Refactor analyzer framework for pluggable analysis configuration --- .completion | 2 +- .../scala/esmeta/analyzer/AbsSemantics.scala | 276 +-- .../scala/esmeta/analyzer/AbsTransfer.scala | 1674 +++++++++-------- .../scala/esmeta/analyzer/AnalysisPoint.scala | 109 +- src/main/scala/esmeta/analyzer/Analyzer.scala | 190 +- .../scala/esmeta/analyzer/ESAnalyzer.scala | 31 +- .../scala/esmeta/analyzer/Optimized.scala | 57 - .../scala/esmeta/analyzer/PruneHelper.scala | 71 - .../scala/esmeta/analyzer/TypeAnalyzer.scala | 223 ++- .../scala/esmeta/analyzer/TypeMismatch.scala | 55 +- src/main/scala/esmeta/analyzer/View.scala | 27 +- .../scala/esmeta/analyzer/domain/AValue.scala | 131 +- .../esmeta/analyzer/domain/AbsRefValue.scala | 17 +- .../scala/esmeta/analyzer/domain/Domain.scala | 105 +- .../esmeta/analyzer/domain/FlatDomain.scala | 183 +- .../esmeta/analyzer/domain/OptionDomain.scala | 125 +- .../esmeta/analyzer/domain/SetDomain.scala | 189 +- .../esmeta/analyzer/domain/SimpleDomain.scala | 141 +- .../{Domain.scala => AbsentDomain.scala} | 7 +- .../domain/absent/AbsentSimpleDomain.scala | 14 + .../analyzer/domain/absent/SimpleDomain.scala | 11 - .../analyzer/domain/absent/package.scala | 8 + .../{Domain.scala => AstValueDomain.scala} | 7 +- .../domain/astValue/AstValueFlatDomain.scala | 25 + .../domain/astValue/AstValueSetDomain.scala | 14 + .../astValue/AstValueSimpleDomain.scala | 13 + .../analyzer/domain/astValue/FlatDomain.scala | 20 - .../analyzer/domain/astValue/SetDomain.scala | 11 - .../domain/astValue/SimpleDomain.scala | 8 - .../analyzer/domain/astValue/package.scala | 13 + .../analyzer/domain/bigInt/BigIntDomain.scala | 11 + .../domain/bigInt/BigIntFlatDomain.scala | 11 + .../domain/bigInt/BigIntSetDomain.scala | 14 + .../domain/bigInt/BigIntSimpleDomain.scala | 13 + .../analyzer/domain/bigInt/Domain.scala | 8 - .../analyzer/domain/bigInt/FlatDomain.scala | 8 - .../analyzer/domain/bigInt/SetDomain.scala | 11 - .../analyzer/domain/bigInt/SimpleDomain.scala | 8 - .../analyzer/domain/bigInt/package.scala | 13 + .../analyzer/domain/bool/BoolDomain.scala | 25 + .../analyzer/domain/bool/BoolFlatDomain.scala | 37 + .../esmeta/analyzer/domain/bool/Domain.scala | 22 - .../analyzer/domain/bool/FlatDomain.scala | 34 - .../analyzer/domain/bool/SimpleDomain.scala | 29 - .../esmeta/analyzer/domain/bool/package.scala | 7 + .../analyzer/domain/clo/CloDomain.scala | 15 + .../analyzer/domain/clo/CloFlatDomain.scala | 11 + .../analyzer/domain/clo/CloSetDomain.scala | 14 + .../analyzer/domain/clo/CloSimpleDomain.scala | 11 + .../esmeta/analyzer/domain/clo/Domain.scala | 12 - .../analyzer/domain/clo/FlatDomain.scala | 8 - .../analyzer/domain/clo/SetDomain.scala | 11 - .../analyzer/domain/clo/SimpleDomain.scala | 8 - .../esmeta/analyzer/domain/clo/package.scala | 13 + .../{Domain.scala => CodeUnitDomain.scala} | 7 +- .../domain/codeUnit/CodeUnitFlatDomain.scala | 13 + .../domain/codeUnit/CodeUnitSetDomain.scala | 14 + .../codeUnit/CodeUnitSimpleDomain.scala | 13 + .../analyzer/domain/codeUnit/FlatDomain.scala | 8 - .../analyzer/domain/codeUnit/SetDomain.scala | 11 - .../domain/codeUnit/SimpleDomain.scala | 10 - .../analyzer/domain/codeUnit/package.scala | 13 + .../analyzer/domain/comp/BasicDomain.scala | 143 -- .../domain/comp/CompBasicDomain.scala | 146 ++ .../analyzer/domain/comp/CompDomain.scala | 62 + .../esmeta/analyzer/domain/comp/Domain.scala | 59 - .../esmeta/analyzer/domain/comp/package.scala | 7 + .../const/{Domain.scala => ConstDomain.scala} | 7 +- .../domain/const/ConstFlatDomain.scala | 11 + .../domain/const/ConstSetDomain.scala | 14 + .../domain/const/ConstSimpleDomain.scala | 11 + .../analyzer/domain/const/FlatDomain.scala | 8 - .../analyzer/domain/const/SetDomain.scala | 11 - .../analyzer/domain/const/SimpleDomain.scala | 8 - .../analyzer/domain/const/package.scala | 13 + .../analyzer/domain/cont/ContDomain.scala | 15 + .../analyzer/domain/cont/ContFlatDomain.scala | 11 + .../analyzer/domain/cont/ContSetDomain.scala | 14 + .../domain/cont/ContSimpleDomain.scala | 11 + .../esmeta/analyzer/domain/cont/Domain.scala | 12 - .../analyzer/domain/cont/FlatDomain.scala | 8 - .../analyzer/domain/cont/SetDomain.scala | 11 - .../analyzer/domain/cont/SimpleDomain.scala | 8 - .../esmeta/analyzer/domain/cont/package.scala | 13 + .../analyzer/domain/heap/BasicDomain.scala | 295 --- .../esmeta/analyzer/domain/heap/Domain.scala | 108 -- .../domain/heap/HeapBasicDomain.scala | 303 +++ .../analyzer/domain/heap/HeapDomain.scala | 111 ++ .../esmeta/analyzer/domain/heap/package.scala | 7 + .../esmeta/analyzer/domain/math/Domain.scala | 8 - .../analyzer/domain/math/FlatDomain.scala | 8 - .../analyzer/domain/math/MathDomain.scala | 11 + .../analyzer/domain/math/MathFlatDomain.scala | 11 + .../analyzer/domain/math/MathSetDomain.scala | 14 + .../domain/math/MathSimpleDomain.scala | 11 + .../analyzer/domain/math/SetDomain.scala | 11 - .../analyzer/domain/math/SimpleDomain.scala | 8 - .../esmeta/analyzer/domain/math/package.scala | 13 + .../analyzer/domain/nt/FlatDomain.scala | 8 - .../nt/{Domain.scala => NtDomain.scala} | 7 +- .../analyzer/domain/nt/NtFlatDomain.scala | 11 + .../analyzer/domain/nt/NtSetDomain.scala | 14 + .../analyzer/domain/nt/NtSimpleDomain.scala | 11 + .../esmeta/analyzer/domain/nt/SetDomain.scala | 11 - .../analyzer/domain/nt/SimpleDomain.scala | 8 - .../esmeta/analyzer/domain/nt/package.scala | 13 + .../nullv/{Domain.scala => NullDomain.scala} | 7 +- .../domain/nullv/NullSimpleDomain.scala | 14 + .../analyzer/domain/nullv/SimpleDomain.scala | 9 - .../analyzer/domain/nullv/package.scala | 7 + .../analyzer/domain/number/FlatDomain.scala | 8 - .../{Domain.scala => NumberDomain.scala} | 7 +- .../domain/number/NumberFlatDomain.scala | 11 + .../domain/number/NumberSetDomain.scala | 14 + .../domain/number/NumberSimpleDomain.scala | 13 + .../analyzer/domain/number/SetDomain.scala | 11 - .../analyzer/domain/number/SimpleDomain.scala | 8 - .../analyzer/domain/number/package.scala | 13 + .../analyzer/domain/obj/BasicDomain.scala | 461 ----- .../esmeta/analyzer/domain/obj/Domain.scala | 80 - .../analyzer/domain/obj/ObjBasicDomain.scala | 467 +++++ .../analyzer/domain/obj/ObjDomain.scala | 83 + .../esmeta/analyzer/domain/obj/package.scala | 7 + .../esmeta/analyzer/domain/package.scala | 39 + .../esmeta/analyzer/domain/part/Domain.scala | 12 - .../analyzer/domain/part/FlatDomain.scala | 8 - .../analyzer/domain/part/PartDomain.scala | 15 + .../analyzer/domain/part/PartFlatDomain.scala | 11 + .../analyzer/domain/part/PartSetDomain.scala | 14 + .../domain/part/PartSimpleDomain.scala | 11 + .../analyzer/domain/part/SetDomain.scala | 11 - .../analyzer/domain/part/SimpleDomain.scala | 8 - .../esmeta/analyzer/domain/part/package.scala | 13 + .../domain/pureValue/BasicDomain.scala | 234 --- .../analyzer/domain/pureValue/Domain.scala | 94 - .../pureValue/PureValueBasicDomain.scala | 237 +++ .../domain/pureValue/PureValueDomain.scala | 97 + .../analyzer/domain/pureValue/package.scala | 9 + .../analyzer/domain/ret/BasicDomain.scala | 92 - .../esmeta/analyzer/domain/ret/Domain.scala | 32 - .../analyzer/domain/ret/RetBasicDomain.scala | 95 + .../analyzer/domain/ret/RetDomain.scala | 35 + .../analyzer/domain/ret/RetTypeDomain.scala | 65 + .../analyzer/domain/ret/TypeDomain.scala | 62 - .../esmeta/analyzer/domain/ret/package.scala | 12 + .../domain/simpleValue/BasicDomain.scala | 174 -- .../analyzer/domain/simpleValue/Domain.scala | 66 - .../simpleValue/SimpleValueBasicDomain.scala | 177 ++ .../simpleValue/SimpleValueDomain.scala | 69 + .../analyzer/domain/simpleValue/package.scala | 9 + .../analyzer/domain/state/BasicDomain.scala | 436 ----- .../esmeta/analyzer/domain/state/Domain.scala | 203 -- .../domain/state/StateBasicDomain.scala | 441 +++++ .../analyzer/domain/state/StateDomain.scala | 207 ++ .../domain/state/StateTypeDomain.scala | 414 ++++ .../analyzer/domain/state/TypeDomain.scala | 409 ---- .../analyzer/domain/state/package.scala | 12 + .../esmeta/analyzer/domain/str/Domain.scala | 8 - .../analyzer/domain/str/SetDomain.scala | 11 - .../analyzer/domain/str/SimpleDomain.scala | 8 - .../str/{FlatDomain.scala => StrDomain.scala} | 7 +- .../analyzer/domain/str/StrFlatDomain.scala | 11 + .../analyzer/domain/str/StrSetDomain.scala | 14 + .../analyzer/domain/str/StrSimpleDomain.scala | 11 + .../esmeta/analyzer/domain/str/package.scala | 13 + .../analyzer/domain/undef/SimpleDomain.scala | 9 - .../undef/{Domain.scala => UndefDomain.scala} | 7 +- .../domain/undef/UndefSimpleDomain.scala | 14 + .../analyzer/domain/undef/package.scala | 7 + .../analyzer/domain/value/BasicDomain.scala | 534 ------ .../esmeta/analyzer/domain/value/Domain.scala | 202 -- .../analyzer/domain/value/TypeDomain.scala | 655 ------- .../domain/value/ValueBasicDomain.scala | 537 ++++++ .../analyzer/domain/value/ValueDomain.scala | 205 ++ .../domain/value/ValueTypeDomain.scala | 662 +++++++ .../analyzer/domain/value/package.scala | 12 + src/main/scala/esmeta/analyzer/package.scala | 284 +-- .../scala/esmeta/analyzer/repl/REPL.scala | 291 +-- .../analyzer/repl/command/CmdBreak.scala | 36 +- .../analyzer/repl/command/CmdContinue.scala | 27 +- .../analyzer/repl/command/CmdEntry.scala | 131 +- .../analyzer/repl/command/CmdExit.scala | 27 +- .../analyzer/repl/command/CmdFindMerged.scala | 39 +- .../analyzer/repl/command/CmdGraph.scala | 37 +- .../analyzer/repl/command/CmdHelp.scala | 39 +- .../analyzer/repl/command/CmdInfo.scala | 87 +- .../analyzer/repl/command/CmdJump.scala | 45 +- .../analyzer/repl/command/CmdListBreak.scala | 31 +- .../esmeta/analyzer/repl/command/CmdLog.scala | 27 +- .../analyzer/repl/command/CmdMove.scala | 87 +- .../analyzer/repl/command/CmdPrint.scala | 70 +- .../analyzer/repl/command/CmdRmBreak.scala | 43 +- .../analyzer/repl/command/CmdStop.scala | 27 +- .../analyzer/repl/command/CmdWorklist.scala | 39 +- .../analyzer/repl/command/Command.scala | 84 +- .../analyzer/repl/command/package.scala | 26 + .../scala/esmeta/analyzer/repl/package.scala | 7 + .../esmeta/analyzer/util/DotPrinter.scala | 151 +- .../scala/esmeta/analyzer/util/Graph.scala | 153 +- .../esmeta/analyzer/util/Stringifier.scala | 257 ++- .../scala/esmeta/analyzer/util/package.scala | 9 + .../scala/esmeta/error/AnalaysisError.scala | 6 - .../esmeta/interpreter/Interpreter.scala | 28 +- src/main/scala/esmeta/phase/Analyze.scala | 14 +- src/main/scala/esmeta/phase/Eval.scala | 7 - src/main/scala/esmeta/phase/TypeCheck.scala | 20 +- src/main/scala/esmeta/ty/AstValueTy.scala | 3 - src/main/scala/esmeta/ty/BoolTy.scala | 3 - src/main/scala/esmeta/ty/CompTy.scala | 5 - src/main/scala/esmeta/ty/ListTy.scala | 3 - src/main/scala/esmeta/ty/NameTy.scala | 3 - src/main/scala/esmeta/ty/PureValueTy.scala | 21 - src/main/scala/esmeta/ty/RecordTy.scala | 3 - src/main/scala/esmeta/ty/SubMapTy.scala | 3 - src/main/scala/esmeta/ty/ValueTy.scala | 6 - .../scala/esmeta/util/IndentParsers.scala | 4 +- .../scala/esmeta/analyzer/AnalyzerTest.scala | 5 + .../esmeta/analyzer/StringifyTinyTest.scala | 6 +- .../scala/esmeta/es/AnalyzeSmallTest.scala | 1 - src/test/scala/esmeta/es/ESTest.scala | 5 +- 220 files changed, 8215 insertions(+), 7760 deletions(-) delete mode 100644 src/main/scala/esmeta/analyzer/Optimized.scala delete mode 100644 src/main/scala/esmeta/analyzer/PruneHelper.scala rename src/main/scala/esmeta/analyzer/domain/absent/{Domain.scala => AbsentDomain.scala} (50%) create mode 100644 src/main/scala/esmeta/analyzer/domain/absent/AbsentSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/absent/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/absent/package.scala rename src/main/scala/esmeta/analyzer/domain/astValue/{Domain.scala => AstValueDomain.scala} (51%) create mode 100644 src/main/scala/esmeta/analyzer/domain/astValue/AstValueFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/astValue/AstValueSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/astValue/AstValueSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/astValue/FlatDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/astValue/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/astValue/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/astValue/package.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/bigInt/BigIntDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/bigInt/BigIntFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/bigInt/BigIntSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/bigInt/BigIntSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/bigInt/Domain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/bigInt/FlatDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/bigInt/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/bigInt/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/bigInt/package.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/bool/BoolDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/bool/BoolFlatDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/bool/Domain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/bool/FlatDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/bool/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/bool/package.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/clo/CloDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/clo/CloFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/clo/CloSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/clo/CloSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/clo/Domain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/clo/FlatDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/clo/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/clo/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/clo/package.scala rename src/main/scala/esmeta/analyzer/domain/codeUnit/{Domain.scala => CodeUnitDomain.scala} (50%) create mode 100644 src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/codeUnit/FlatDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/codeUnit/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/codeUnit/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/codeUnit/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/comp/BasicDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/comp/CompBasicDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/comp/CompDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/comp/Domain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/comp/package.scala rename src/main/scala/esmeta/analyzer/domain/const/{Domain.scala => ConstDomain.scala} (51%) create mode 100644 src/main/scala/esmeta/analyzer/domain/const/ConstFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/const/ConstSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/const/ConstSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/const/FlatDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/const/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/const/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/const/package.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/cont/ContDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/cont/ContFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/cont/ContSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/cont/ContSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/cont/Domain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/cont/FlatDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/cont/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/cont/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/cont/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/heap/BasicDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/heap/Domain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/heap/HeapBasicDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/heap/HeapDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/heap/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/math/Domain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/math/FlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/math/MathDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/math/MathFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/math/MathSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/math/MathSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/math/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/math/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/math/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/nt/FlatDomain.scala rename src/main/scala/esmeta/analyzer/domain/nt/{Domain.scala => NtDomain.scala} (51%) create mode 100644 src/main/scala/esmeta/analyzer/domain/nt/NtFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/nt/NtSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/nt/NtSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/nt/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/nt/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/nt/package.scala rename src/main/scala/esmeta/analyzer/domain/nullv/{Domain.scala => NullDomain.scala} (51%) create mode 100644 src/main/scala/esmeta/analyzer/domain/nullv/NullSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/nullv/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/nullv/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/number/FlatDomain.scala rename src/main/scala/esmeta/analyzer/domain/number/{Domain.scala => NumberDomain.scala} (50%) create mode 100644 src/main/scala/esmeta/analyzer/domain/number/NumberFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/number/NumberSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/number/NumberSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/number/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/number/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/number/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/obj/BasicDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/obj/Domain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/obj/ObjBasicDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/obj/ObjDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/obj/package.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/part/Domain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/part/FlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/part/PartDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/part/PartFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/part/PartSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/part/PartSimpleDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/part/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/part/SimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/part/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/pureValue/BasicDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/pureValue/Domain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/pureValue/PureValueBasicDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/pureValue/PureValueDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/pureValue/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/ret/BasicDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/ret/Domain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/ret/RetBasicDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/ret/RetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/ret/RetTypeDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/ret/TypeDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/ret/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/simpleValue/BasicDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/simpleValue/Domain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/simpleValue/SimpleValueBasicDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/simpleValue/SimpleValueDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/simpleValue/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/state/BasicDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/state/Domain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/state/StateBasicDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/state/StateDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/state/StateTypeDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/state/TypeDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/state/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/str/Domain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/str/SetDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/str/SimpleDomain.scala rename src/main/scala/esmeta/analyzer/domain/str/{FlatDomain.scala => StrDomain.scala} (52%) create mode 100644 src/main/scala/esmeta/analyzer/domain/str/StrFlatDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/str/StrSetDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/str/StrSimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/str/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/undef/SimpleDomain.scala rename src/main/scala/esmeta/analyzer/domain/undef/{Domain.scala => UndefDomain.scala} (50%) create mode 100644 src/main/scala/esmeta/analyzer/domain/undef/UndefSimpleDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/undef/package.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/value/BasicDomain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/value/Domain.scala delete mode 100644 src/main/scala/esmeta/analyzer/domain/value/TypeDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/value/ValueBasicDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/value/ValueDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala create mode 100644 src/main/scala/esmeta/analyzer/domain/value/package.scala create mode 100644 src/main/scala/esmeta/analyzer/repl/command/package.scala create mode 100644 src/main/scala/esmeta/analyzer/repl/package.scala create mode 100644 src/main/scala/esmeta/analyzer/util/package.scala diff --git a/.completion b/.completion index 36122f4201..17f8279a0e 100644 --- a/.completion +++ b/.completion @@ -19,7 +19,7 @@ _esmeta_completions() { buildcfgOpt="-build-cfg:log -build-cfg:dot -build-cfg:pdf" tycheckOpt="-tycheck:target -tycheck:repl -tycheck:repl-continue -tycheck:ignore -tycheck:update-ignore -tycheck:log" parseOpt="-parse:debug" - evalOpt="-eval:timeout -eval:tycheck -eval:multiple -eval:log" + evalOpt="-eval:timeout -eval:multiple -eval:log" webOpt="-web:port" test262testOpt="-test262-test:target -test262-test:progress -test262-test:coverage -test262-test:timeout -test262-test:with-yet -test262-test:log -test262-test:concurrent -test262-test:verbose" injectOpt="-inject:defs -inject:out -inject:log" diff --git a/src/main/scala/esmeta/analyzer/AbsSemantics.scala b/src/main/scala/esmeta/analyzer/AbsSemantics.scala index 1b365fd3d4..fced9d4cce 100644 --- a/src/main/scala/esmeta/analyzer/AbsSemantics.scala +++ b/src/main/scala/esmeta/analyzer/AbsSemantics.scala @@ -11,137 +11,155 @@ import esmeta.util.BaseUtils.* import scala.Console.* import scala.annotation.tailrec -/** abstract semantics */ -class AbsSemantics( - /** abstract states in each node point */ - var npMap: Map[NodePoint[Node], AbsState] = Map(), - /** abstract states in each return point */ - var rpMap: Map[ReturnPoint, AbsRet] = Map(), - /** abstract states right before calling functions */ - var callInfo: Map[NodePoint[Call], AbsState] = Map(), - /** return edges */ - var retEdges: Map[ReturnPoint, Set[NodePoint[Call]]] = Map(), - /** loop out edges */ - var loopOut: Map[View, Set[View]] = Map(), - /** current control point */ - var curCp: Option[ControlPoint] = None, -) { - - /** a worklist of control points */ - val worklist: Worklist[ControlPoint] = QueueWorklist(npMap.keySet) - - /** the number of iterations */ - var iter: Int = 0 - - /** count for each control point */ - var counter: Map[ControlPoint, Int] = Map() - def getCount(cp: ControlPoint): Int = counter.getOrElse(cp, 0) - - /** RunJobs function */ - val runJobs = cfg.fnameMap("RunJobs") - - /** get return point of RunJobs */ - val runJobsRp = ReturnPoint(runJobs, View()) - - /** get abstract return values and states of RunJobs */ - def finalResult: AbsRet = this(runJobsRp) - - /** set start time of analyzer */ - val startTime: Long = System.currentTimeMillis - - /** set of analyzed functions */ - def analyzedFuncs: Set[Func] = - npMap.keySet.map(_.func) ++ rpMap.keySet.map(_.func) - - /** get return edges */ - def getRetEdges(rp: ReturnPoint): Set[NodePoint[Call]] = - retEdges.getOrElse(rp, Set()) - - /** lookup for node points */ - def apply(np: NodePoint[Node]): AbsState = npMap.getOrElse(np, AbsState.Bot) - - /** lookup for return points */ - def apply(rp: ReturnPoint): AbsRet = rpMap.getOrElse(rp, AbsRet.Bot) - - /** update internal map */ - def +=(pair: (NodePoint[Node], AbsState)): Unit = - val (np, newSt) = pair - val oldSt = this(np) - if (!oldSt.isBottom && USE_REPL) REPL.merged = true - if (!newSt.isBottom && !(newSt ⊑ oldSt)) - npMap += np -> (oldSt ⊔ newSt) - worklist += np - - /** loop transition for next views */ - def loopNext(view: View): View = view.loops match - case LoopCtxt(loop, k) :: rest if IR_SENS => - view.copy(loops = LoopCtxt(loop, k + 1) :: rest) - case _ => view - - /** loop transition for function enter */ - def loopEnter(view: View, loop: Branch): View = - val loopView = - if (IR_SENS) - view.copy( - loops = LoopCtxt(loop, 0) :: view.loops, - intraLoopDepth = view.intraLoopDepth + 1, - ) - else view - loopOut += loopView -> (loopOut.getOrElse(loopView, Set()) + view) - loopView - - /** loop transition for bases */ - def loopBase(view: View): View = view.loops match - case LoopCtxt(loop, k) :: rest if IR_SENS => - view.copy(loops = LoopCtxt(loop, 0) :: rest) - case _ => view - - /** loop transition for function exits */ - def loopExit(view: View): View = if (IR_SENS) { - val views = loopOut.getOrElse(loopBase(view), Set()) - views.size match - case 0 => error("invalid loop exit") - case 1 => views.head - case _ => exploded("loop is too merged.") - } else view - - /** get entry views of loops */ - @tailrec - final def getEntryView(view: View): View = - if (!IR_SENS | view.intraLoopDepth == 0) view - else getEntryView(loopExit(view)) - - /** get abstract state of control points */ - def getState(cp: ControlPoint): AbsState = cp match - case np: NodePoint[_] => this(np) - case rp: ReturnPoint => this(rp).state - - /** get string for result of control points */ - def getString( - cp: ControlPoint, - color: String, - detail: Boolean, - ): String = - val func = cp.func.name - val cpStr = cp.toString(detail = detail) - val k = setColor(color)(cpStr) - cp match - case np: NodePoint[_] => - val st = this(np).getString(detail = detail) - s"""$k -> $st +trait AbsSemanticsDecl { self: Analyzer => + + /** abstract semantics */ + class AbsSemantics( + /** abstract states in each node point */ + var npMap: Map[NodePoint[Node], AbsState] = Map(), + /** abstract states in each return point */ + var rpMap: Map[ReturnPoint, AbsRet] = Map(), + /** abstract states right before calling functions */ + var callInfo: Map[NodePoint[Call], AbsState] = Map(), + /** return edges */ + var retEdges: Map[ReturnPoint, Set[NodePoint[Call]]] = Map(), + /** loop out edges */ + var loopOut: Map[View, Set[View]] = Map(), + /** current control point */ + var curCp: Option[ControlPoint] = None, + /** the number of iterations */ + var iter: Int = 0, + /** count for each control point */ + var counter: Map[ControlPoint, Int] = Map(), + ) { + + /** a worklist of control points */ + var worklist: Worklist[ControlPoint] = QueueWorklist(npMap.keySet) + + /** set start time of analyzer */ + var startTime: Long = System.currentTimeMillis + + /** reset the analysis */ + def reset(that: AbsSemantics): Unit = { + npMap = that.npMap + rpMap = that.rpMap + callInfo = that.callInfo + retEdges = that.retEdges + loopOut = that.loopOut + curCp = that.curCp + iter = that.iter + counter = that.counter + worklist = QueueWorklist(npMap.keySet) + startTime = System.currentTimeMillis + } + + /** increase the counter */ + def count(cp: ControlPoint): Unit = + counter += cp -> (counter.getOrElse(cp, 0) + 1) + + /** RunJobs function */ + val runJobs = cfg.fnameMap("RunJobs") + + /** get return point of RunJobs */ + val runJobsRp = ReturnPoint(runJobs, View()) + + /** get abstract return values and states of RunJobs */ + def finalResult: AbsRet = this(runJobsRp) + + /** set of analyzed functions */ + def analyzedFuncs: Set[Func] = + npMap.keySet.map(_.func) ++ rpMap.keySet.map(_.func) + + /** get return edges */ + def getRetEdges(rp: ReturnPoint): Set[NodePoint[Call]] = + retEdges.getOrElse(rp, Set()) + + /** lookup for node points */ + def apply(np: NodePoint[Node]): AbsState = npMap.getOrElse(np, AbsState.Bot) + + /** lookup for return points */ + def apply(rp: ReturnPoint): AbsRet = rpMap.getOrElse(rp, AbsRet.Bot) + + /** update internal map */ + def +=(pair: (NodePoint[Node], AbsState)): Unit = + val (np, newSt) = pair + val oldSt = this(np) + if (!oldSt.isBottom && useRepl) Repl.merged = true + if (!newSt.isBottom && !(newSt ⊑ oldSt)) + npMap += np -> (oldSt ⊔ newSt) + worklist += np + + /** loop transition for next views */ + def loopNext(view: View): View = view.loops match + case LoopCtxt(loop, k) :: rest if irSens => + view.copy(loops = LoopCtxt(loop, k + 1) :: rest) + case _ => view + + /** loop transition for function enter */ + def loopEnter(view: View, loop: Branch): View = + val loopView = + if (irSens) + view.copy( + loops = LoopCtxt(loop, 0) :: view.loops, + intraLoopDepth = view.intraLoopDepth + 1, + ) + else view + loopOut += loopView -> (loopOut.getOrElse(loopView, Set()) + view) + loopView + + /** loop transition for bases */ + def loopBase(view: View): View = view.loops match + case LoopCtxt(loop, k) :: rest if irSens => + view.copy(loops = LoopCtxt(loop, 0) :: rest) + case _ => view + + /** loop transition for function exits */ + def loopExit(view: View): View = if (irSens) { + val views = loopOut.getOrElse(loopBase(view), Set()) + views.size match + case 0 => error("invalid loop exit") + case 1 => views.head + case _ => exploded("loop is too merged.") + } else view + + /** get entry views of loops */ + @tailrec + final def getEntryView(view: View): View = + if (!irSens | view.intraLoopDepth == 0) view + else getEntryView(loopExit(view)) + + /** get abstract state of control points */ + def getState(cp: ControlPoint): AbsState = cp match + case np: NodePoint[_] => this(np) + case rp: ReturnPoint => this(rp).state + + /** get string for result of control points */ + def getString( + cp: ControlPoint, + color: String, + detail: Boolean, + ): String = + val func = cp.func.name + val cpStr = cp.toString(detail = detail) + val k = setColor(color)(cpStr) + cp match + case np: NodePoint[_] => + val st = this(np).getString(detail = detail) + s"""$k -> $st |${np.node}""".stripMargin - case rp: ReturnPoint => - val st = this(rp).getString(detail = detail) - s"""$k -> $st""" + case rp: ReturnPoint => + val st = this(rp).getString(detail = detail) + s"""$k -> $st""" - /** check reachability */ - def reachable(np: NodePoint[Node]): Boolean = !apply(np).isBottom - def reachable(rp: ReturnPoint): Boolean = !apply(rp).isBottom + /** check reachability */ + def reachable(np: NodePoint[Node]): Boolean = !apply(np).isBottom + def reachable(rp: ReturnPoint): Boolean = !apply(rp).isBottom - /** conversion to string */ - override def toString: String = shortString + /** conversion to string */ + override def toString: String = shortString - /** conversion to short string */ - def shortString: String = - s"- ${analyzedFuncs.size} functions are analyzed in $iter iterations." + /** conversion to short string */ + def shortString: String = + s"- ${analyzedFuncs.size} functions are analyzed in $iter iterations." + } } diff --git a/src/main/scala/esmeta/analyzer/AbsTransfer.scala b/src/main/scala/esmeta/analyzer/AbsTransfer.scala index 8e6aea0db0..9ffbfeab51 100644 --- a/src/main/scala/esmeta/analyzer/AbsTransfer.scala +++ b/src/main/scala/esmeta/analyzer/AbsTransfer.scala @@ -15,808 +15,928 @@ import esmeta.util.* import esmeta.util.BaseUtils.* import scala.annotation.tailrec -/** abstract transfer function */ -trait AbsTransfer extends Optimized with PruneHelper { - - /** loading monads */ - import AbsState.monad.* - - /** fixpiont computation */ - @tailrec - final def fixpoint: Unit = sem.worklist.next match - case Some(cp) => - // set the current control point - sem.curCp = Some(cp) - // count how many visited for each control point - sem.counter += cp -> (sem.getCount(cp) + 1) - // increase iteration number - sem.iter += 1 - // check time limit - if (sem.iter % CHECK_PERIOD == 0) TIME_LIMIT.map(limit => { - val duration = (System.currentTimeMillis - sem.startTime) / 1000 - if (duration > limit) exploded("timeout") - }) - // text-based debugging - if (DEBUG) println(s"${cp.func.name}:$cp") - // run REPL - if (USE_REPL) REPL(this, cp) - // abstract transfer for the current control point - else apply(cp) - // keep going - fixpoint - case None => - // set the current control point - sem.curCp = None - // finalize REPL - if (USE_REPL) REPL.finished - - /** transfer function for control points */ - def apply(cp: ControlPoint): Unit = cp match - case (np: NodePoint[_]) => this(np) - case (rp: ReturnPoint) => this(rp) - - /** transfer function for node points */ - def apply(np: NodePoint[_]): Unit = { - // record current control point for alarm - given NodePoint[_] = np - val st = sem(np) - val NodePoint(func, node, view) = np - - node match { - case Block(_, insts, next) => - val newSt = insts.foldLeft(st) { - case (nextSt, inst) => - if (!nextSt.isBottom) transfer(inst)(nextSt) - else nextSt - } - next.foreach(to => sem += getNextNp(np, to) -> newSt) - case call: Call => - val (_, newSt) = (for { - v <- transfer(call) - _ <- - if (v.isBottom) put(AbsState.Bot) - else modify(_.defineLocal(call.lhs -> v)) - } yield ())(st) - call.next.foreach(to => sem += getNextNp(np, to) -> newSt) - case br @ Branch(_, kind, cond, thenNode, elseNode) => - (for { - v <- transfer(cond) - newSt <- get - } yield { - if (AVT ⊑ v) - thenNode.foreach(to => - sem += getNextNp(np, to) -> prune(cond, true)(newSt), - ) - if (AVF ⊑ v) - elseNode.foreach(to => - sem += getNextNp(np, to, br.isLoop) -> prune(cond, false)(newSt), - ) - })(st) - } - } +trait AbsTransferDecl { self: Analyzer => - /** get next node point */ - def getNextNp( - fromCp: NodePoint[Node], - to: Node, - loopOut: Boolean = false, - ): NodePoint[Node] = - val NodePoint(func, from, view) = fromCp - - // handle loop sensitivity - val fromView = if (loopOut) sem.loopExit(view) else view - val toView = to match - case br: Branch if br.isLoop => - if (from.isLoopPred) sem.loopEnter(view, br) - else sem.loopNext(view) - case _ => fromView - - // next node point - NodePoint(func, to, toView) - - /** transfer function for return points */ - def apply(rp: ReturnPoint): Unit = { - var AbsRet(value, st) = sem(rp) - - // proper type handle - Interpreter.setTypeMap - .get(rp.func.name) - .map(ty => { - if (!value.unwrapCompletion.isBottom) { - val (newV, newSt) = st.setType(value.unwrapCompletion, ty) - // wrap completion by conditions specified in - // [5.2.3.5 Implicit Normal Completion] - // (https://tc39.es/ecma262/#sec-implicit-normal-completion) - value = if (rp.func.isReturnComp) newV.wrapCompletion else newV - st = newSt - } - }) - - // return wrapped values - for { - np @ NodePoint(func, call, view) <- sem.getRetEdges(rp) - nextNode <- call.next - } { - val callerSt = sem.callInfo(np) - val nextNp = NodePoint( - func, - nextNode, - nextNode match { - case br: Branch if br.isLoop => sem.loopEnter(view, br) - case _ => view - }, - ) + /** abstract transfer function */ + trait AbsTransfer { - val newSt = st.doReturn( - callerSt, - call.lhs -> value, - ) + /** loading monads */ + import AbsState.monad.* - sem += nextNp -> newSt + /** fixpiont computation */ + @tailrec + final def fixpoint: Unit = sem.worklist.next match + case Some(cp) => + // set the current control point + sem.curCp = Some(cp) + // count how many visited for each control point + sem.count(cp) + // increase iteration number + sem.iter += 1 + // check time limit + if (sem.iter % checkPeriod == 0) timeLimit.map(limit => { + val duration = (System.currentTimeMillis - sem.startTime) / 1000 + if (duration > limit) exploded("timeout") + }) + // text-based debugging + if (debug) println(s"${cp.func.name}:$cp") + // run REPL + if (useRepl) Repl(this, cp) + // abstract transfer for the current control point + else apply(cp) + // keep going + fixpoint + case None => + // set the current control point + sem.curCp = None + // finalize REPL + if (useRepl) Repl.finished + + /** transfer function for control points */ + def apply(cp: ControlPoint): Unit = cp match + case (np: NodePoint[_]) => this(np) + case (rp: ReturnPoint) => this(rp) + + /** transfer function for node points */ + def apply(np: NodePoint[_]): Unit = { + // record current control point for alarm + given NodePoint[_] = np + val st = sem(np) + val NodePoint(func, node, view) = np + + node match { + case Block(_, insts, next) => + val newSt = insts.foldLeft(st) { + case (nextSt, inst) => + if (!nextSt.isBottom) transfer(inst)(nextSt) + else nextSt + } + next.foreach(to => sem += getNextNp(np, to) -> newSt) + case call: Call => + val (_, newSt) = (for { + v <- transfer(call) + _ <- + if (v.isBottom) put(AbsState.Bot) + else modify(_.defineLocal(call.lhs -> v)) + } yield ())(st) + call.next.foreach(to => sem += getNextNp(np, to) -> newSt) + case br @ Branch(_, kind, cond, thenNode, elseNode) => + (for { + v <- transfer(cond) + newSt <- get + } yield { + if (AVT ⊑ v) + thenNode.foreach(to => + sem += getNextNp(np, to) -> prune(cond, true)(newSt), + ) + if (AVF ⊑ v) + elseNode.foreach(to => + sem += getNextNp(np, to, br.isLoop) -> prune(cond, false)( + newSt, + ), + ) + })(st) + } } - } - // transfer function for expressions - def apply(cp: ControlPoint, expr: Expr): AbsValue = { - // record current control point for alarm - given ControlPoint = cp - val st = sem.getState(cp) - transfer(expr)(st)._1 - } + /** get next node point */ + def getNextNp( + fromCp: NodePoint[Node], + to: Node, + loopOut: Boolean = false, + ): NodePoint[Node] = + val NodePoint(func, from, view) = fromCp + + // handle loop sensitivity + val fromView = if (loopOut) sem.loopExit(view) else view + val toView = to match + case br: Branch if br.isLoop => + if (from.isLoopPred) sem.loopEnter(view, br) + else sem.loopNext(view) + case _ => fromView + + // next node point + NodePoint(func, to, toView) + + /** transfer function for return points */ + def apply(rp: ReturnPoint): Unit = { + var AbsRet(value, st) = sem(rp) + + // proper type handle + Interpreter.setTypeMap + .get(rp.func.name) + .map(ty => { + if (!value.unwrapCompletion.isBottom) { + val (newV, newSt) = st.setType(value.unwrapCompletion, ty) + // wrap completion by conditions specified in + // [5.2.3.5 Implicit Normal Completion] + // (https://tc39.es/ecma262/#sec-implicit-normal-completion) + value = if (rp.func.isReturnComp) newV.wrapCompletion else newV + st = newSt + } + }) - /** sdo with default case */ - val defaultCases = List( - "Contains", - "AllPrivateIdentifiersValid", - "ContainsArguments", - ) - - /** get syntax-directed operation (SDO) */ - val getSDO = cached[(Ast, String), Option[(Ast, Func)]] { - case (ast, operation) => - val fnameMap = cfg.fnameMap - ast.chains.foldLeft[Option[(Ast, Func)]](None) { - case (None, ast0) => - val subIdx = getSubIdx(ast0) - val fname = s"${ast0.name}[${ast0.idx},${subIdx}].$operation" - fnameMap.get(fname) match - case Some(sdo) => Some(ast0, sdo) - case None if defaultCases contains operation => - Some(ast0, fnameMap(s".$operation")) - case _ => None - case (res: Some[_], _) => res - } - } + // return wrapped values + for { + np @ NodePoint(func, call, view) <- sem.getRetEdges(rp) + nextNode <- call.next + } { + val callerSt = sem.callInfo(np) + val nextNp = NodePoint( + func, + nextNode, + nextNode match { + case br: Branch if br.isLoop => sem.loopEnter(view, br) + case _ => view + }, + ) - /** get sub index of parsed Ast */ - val getSubIdx = cached[Ast, Int] { - case lex: Lexical => 0 - case Syntactic(name, _, rhsIdx, children) => - val rhs = cfg.grammar.nameMap(name).rhsList(rhsIdx) - val optionals = (for { - ((_, opt), child) <- rhs.ntsWithOptional zip children if opt - } yield !child.isEmpty) - optionals.reverse.zipWithIndex.foldLeft(0) { - case (acc, (true, idx)) => acc + scala.math.pow(2, idx).toInt - case (acc, _) => acc + val newSt = st.doReturn( + callerSt, + call.lhs -> value, + ) + + sem += nextNp -> newSt } - } + } - /** transfer function for normal instructions */ - def transfer(inst: NormalInst)(using cp: NodePoint[_]): Updater = inst match { - case IExpr(expr) => - for { - v <- transfer(expr) - } yield v - case ILet(id, expr) => - for { - v <- transfer(expr) - _ <- modify(_.defineLocal(id -> v)) - } yield () - case IAssign(ref, expr) => - for { - rv <- transfer(ref) - v <- transfer(expr) - _ <- modify(_.update(rv, v)) - } yield () - case IDelete(ref) => - for { - rv <- transfer(ref) - _ <- modify(_.delete(rv)) - } yield () - case IPush(expr, list, front) => - for { - l <- transfer(list) - v <- transfer(expr) - _ <- modify(_.push(l, v, front)) - } yield () - case IRemoveElem(list, elem) => - for { - l <- transfer(list) - v <- transfer(elem) - _ <- modify(_.remove(l, v)) - } yield () - case inst @ IReturn(expr) => - for { - v <- transfer(expr) - _ <- doReturn(inst, v) - _ <- put(AbsState.Bot) - } yield () - case IAssert(expr: EYet) => - st => st /* skip not yet compiled assertions */ - case IAssert(expr) => - for { - v <- transfer(expr) - _ <- modify(prune(expr, true)) - _ <- if (v ⊑ AVF) put(AbsState.Bot) else pure(()) - } yield () - case IPrint(expr) => st => st /* skip */ - case INop() => st => st /* skip */ - } + // transfer function for expressions + def apply(cp: ControlPoint, expr: Expr): AbsValue = { + // record current control point for alarm + given ControlPoint = cp + val st = sem.getState(cp) + transfer(expr)(st)._1 + } - /** transfer function for call instructions */ - def transfer(call: Call)(using cp: NodePoint[_]): Result[AbsValue] = - val callerNp = NodePoint(cp.func, call, cp.view) - call.callInst match { - case OptimizedCall(result) => result - case ICall(_, fexpr, args) => - for { - fv <- transfer(fexpr) - as <- join(args.map(transfer)) - st <- get - } yield { - // closure call (unsound for inifinitely many closures) - for (AClo(func, captured) <- fv.clo.toIterable(stop = false)) - doCall(callerNp, st, func, as, captured) - // continuation call (unsound for inifinitely many continuations) - for (ACont(target, captured) <- fv.cont) { - val as0 = - as.map(v => if (cp.func.isReturnComp) v.wrapCompletion else v) - val newLocals = getLocals( - CallPoint(callerNp, target), - as0, - cont = true, - method = false, - ) ++ captured - sem += target -> st.copied(locals = newLocals) - } - AbsValue.Bot - } - case IMethodCall(_, base, method, args) => - for { - rv <- transfer(base) - bv <- transfer(rv) - // TODO do not explicitly store methods in object but use a type - // model when accessing methods - fv <- get(_.get(bv, AbsValue(Str(method)))) - as <- join(args.map(transfer)) - st <- get - } yield { - for (AClo(func, _) <- fv.clo) - doCall( - callerNp, - st, - func, - bv.refineThis(func) :: as, - method = true, - ) - AbsValue.Bot - } - case ISdoCall(_, base, method, args) => - for { - bv <- transfer(base) - as <- join(args.map(transfer)) - st <- get - } yield { - var newV: AbsValue = AbsValue.Bot - bv.getSingle match - case One(AstValue(syn: Syntactic)) => - getSDO((syn, method)) match - case Some((ast0, sdo)) => - doCall( - callerNp, - st, - sdo, - AbsValue(ast0) :: as, - method = true, - ) - case None => error("invalid sdo") - case One(AstValue(lex: Lexical)) => - newV ⊔= AbsValue(Interpreter.eval(lex, method)) - case Many => - // lexical sdo - newV ⊔= bv.getLexical(method) - - // syntactic sdo - for ((sdo, ast) <- bv.getSDO(method)) - doCall(callerNp, st, sdo, ast :: as, method = true) - case _ => /* do nothing */ - newV + /** sdo with default case */ + val defaultCases = List( + "Contains", + "AllPrivateIdentifiersValid", + "ContainsArguments", + ) + + /** get syntax-directed operation (SDO) */ + val getSDO = cached[(Ast, String), Option[(Ast, Func)]] { + case (ast, operation) => + val fnameMap = cfg.fnameMap + ast.chains.foldLeft[Option[(Ast, Func)]](None) { + case (None, ast0) => + val subIdx = getSubIdx(ast0) + val fname = s"${ast0.name}[${ast0.idx},${subIdx}].$operation" + fnameMap.get(fname) match + case Some(sdo) => Some(ast0, sdo) + case None if defaultCases contains operation => + Some(ast0, fnameMap(s".$operation")) + case _ => None + case (res: Some[_], _) => res } } - /** transfer function for expressions */ - def transfer(expr: Expr)(using cp: ControlPoint): Result[AbsValue] = - expr match { - case EComp(ty, value, target) => - for { - tyV <- transfer(ty) - v <- transfer(value) - targetV <- transfer(target) - } yield AbsValue.createCompletion(tyV, v, targetV) - case EIsCompletion(expr) => - for { - v <- transfer(expr) - } yield v.isCompletion - case riaExpr @ EReturnIfAbrupt(ERef(ref), check) => - for { - rv <- transfer(ref) - v <- transfer(rv) - newV <- returnIfAbrupt(riaExpr, v, check) - _ <- - if (!newV.isBottom) modify(_.update(rv, newV)) - else put(AbsState.Bot) - } yield newV - case riaExpr @ EReturnIfAbrupt(expr, check) => - for { - v <- transfer(expr) - newV <- returnIfAbrupt(riaExpr, v, check) - } yield newV - case EPop(list, front) => - for { - v <- transfer(list) - pv <- id(_.pop(v, front)) - } yield pv - case EParse(code, rule) => - for { - c <- transfer(code) - r <- transfer(rule) - } yield c.parse(r) - case ENt(name, params) => AbsValue(Nt(name, params)) - case ESourceText(expr) => - for { v <- transfer(expr) } yield v.sourceText - case e @ EGetChildren(ast) => - val asite = AllocSite(e.asite, cp.view) - for { - av <- transfer(ast) - lv <- id(_.getChildren(asite, av)) - } yield lv - case e @ EGetItems(nt, ast) => - val asite = AllocSite(e.asite, cp.view) - for { - nv <- transfer(nt) - av <- transfer(ast) - lv <- id(_.getItems(asite, nv, av)) - } yield lv - case EYet(msg) => - if (YET_THROW) notSupported(msg) - else AbsValue.Bot - case EContains(list, elem) => - for { - l <- transfer(list) - v <- transfer(elem) - st <- get - } yield st.contains(l, v) - case ESubstring(expr, from, None) => - for { - v <- transfer(expr) - f <- transfer(from) - } yield v.substring(f) - case ESubstring(expr, from, Some(to)) => - for { - v <- transfer(expr) - f <- transfer(from) - t <- transfer(to) - } yield v.substring(f, t) - case ETrim(expr, leading, trailing) => - for { - v <- transfer(expr) - } yield v.trim(leading, trailing) - case ERef(ref) => - for { - rv <- transfer(ref) - v <- transfer(rv) - } yield v - case EUnary(uop, expr) => - for { - v <- transfer(expr) - v0 <- get(transfer(_, uop, v)) - } yield v0 - case EBinary(BOp.And, left, right) => - shortCircuit(BOp.And, left, right) - case EBinary(BOp.Or, left, right) => shortCircuit(BOp.Or, left, right) - case EBinary(BOp.Eq, ERef(ref), EAbsent()) => - for { - rv <- transfer(ref) - b <- get(_.exists(rv)) - } yield !b - case EBinary(bop, left, right) => - for { - lv <- transfer(left) - rv <- transfer(right) - v <- get(transfer(_, bop, lv, rv)) - } yield v - case EVariadic(vop, exprs) => - for { - vs <- join(exprs.map(transfer)) - } yield transfer(vop, vs) - case EClamp(target, lower, upper) => - for { - v <- transfer(target) - lv <- transfer(lower) - uv <- transfer(upper) - } yield v.clamp(lv, uv) - case EMathOp(mop, exprs) => - for { - vs <- join(exprs.map(transfer)) - } yield transfer(mop, vs) - case EConvert(cop, expr) => - import COp.* - for { - v <- transfer(expr) - r <- cop match - case ToStr(Some(radix)) => transfer(radix) - case ToStr(None) => pure(AbsValue(Math(10))) - case _ => pure(AbsValue.Bot) - } yield v.convertTo(cop, r) - case ETypeOf(base) => - for { - v <- transfer(base) - st <- get - } yield v.typeOf(st) - case ETypeCheck(expr, tyExpr) => - for { - v <- transfer(expr) - tv <- transfer(tyExpr) - st <- get - } yield tv.getSingle match - case One(Str(s)) => v.typeCheck(s, st) - case One(Nt(n, _)) => v.typeCheck(n, st) - case _ => AbsValue.boolTop - - case EClo(fname, cap) => - cfg.fnameMap.get(fname) match { - case Some(f) => - for { - st <- get - captured = cap.map(x => x -> st.lookupLocal(x)).toMap - } yield AbsValue(AClo(f, captured)) - case None => - for { _ <- put(AbsState.Bot) } yield AbsValue.Bot + /** get sub index of parsed Ast */ + val getSubIdx = cached[Ast, Int] { + case lex: Lexical => 0 + case Syntactic(name, _, rhsIdx, children) => + val rhs = cfg.grammar.nameMap(name).rhsList(rhsIdx) + val optionals = (for { + ((_, opt), child) <- rhs.ntsWithOptional zip children if opt + } yield !child.isEmpty) + optionals.reverse.zipWithIndex.foldLeft(0) { + case (acc, (true, idx)) => acc + scala.math.pow(2, idx).toInt + case (acc, _) => acc } - case ECont(fname) => - for { - st <- get - func = cfg.fnameMap(fname) - target = NodePoint(func, func.entry, cp.view) - captured = st.locals.collect { case (x: Name, av) => x -> av } - // return edges for resumed evaluation - currRp = ReturnPoint(cp.func, cp.view) - contRp = ReturnPoint(func, cp.view) - _ = sem.retEdges += (contRp -> sem.retEdges.getOrElse(currRp, Set())) - } yield AbsValue(ACont(target, captured)) - case ERandom() => pure(AbsValue.numberTop) - case ESyntactic(name, args, rhsIdx, children) => - for { - cs <- join(children.map { - case Some(child) => transfer(child).map(Some(_)) - case None => pure(None) - }) - } yield { - if (cs.exists(cOpt => cOpt.fold(false)(_.isBottom))) AbsValue.Bot - else { - val cs0 = cs.map(cOpt => - cOpt.map(_.getSingle match { - case One(AstValue(child)) => child - case _ => exploded("ESyntactic") - }), - ) - AbsValue(Syntactic(name, args, rhsIdx, cs0)) + } + + /** transfer function for normal instructions */ + def transfer(inst: NormalInst)(using cp: NodePoint[_]): Updater = + inst match { + case IExpr(expr) => + for { + v <- transfer(expr) + } yield v + case ILet(id, expr) => + for { + v <- transfer(expr) + _ <- modify(_.defineLocal(id -> v)) + st <- get + } yield () + case IAssign(ref, expr) => + for { + rv <- transfer(ref) + v <- transfer(expr) + _ <- modify(_.update(rv, v)) + } yield () + case IDelete(ref) => + for { + rv <- transfer(ref) + _ <- modify(_.delete(rv)) + } yield () + case IPush(expr, list, front) => + for { + l <- transfer(list) + v <- transfer(expr) + _ <- modify(_.push(l, v, front)) + } yield () + case IRemoveElem(list, elem) => + for { + l <- transfer(list) + v <- transfer(elem) + _ <- modify(_.remove(l, v)) + } yield () + case inst @ IReturn(expr) => + for { + v <- transfer(expr) + _ <- doReturn(inst, v) + _ <- put(AbsState.Bot) + } yield () + case IAssert(expr: EYet) => + st => st /* skip not yet compiled assertions */ + case IAssert(expr) => + for { + v <- transfer(expr) + _ <- modify(prune(expr, true)) + _ <- if (v ⊑ AVF) put(AbsState.Bot) else pure(()) + } yield () + case IPrint(expr) => st => st /* skip */ + case INop() => st => st /* skip */ + } + + /** transfer function for call instructions */ + def transfer(call: Call)(using cp: NodePoint[_]): Result[AbsValue] = + val callerNp = NodePoint(cp.func, call, cp.view) + call.callInst match { + case OptimizedCall(result) => result + case ICall(_, fexpr, args) => + for { + fv <- transfer(fexpr) + as <- join(args.map(transfer)) + st <- get + } yield { + // closure call (unsound for inifinitely many closures) + for (AClo(func, captured) <- fv.clo.toIterable(stop = false)) + doCall(callerNp, st, func, as, captured) + // continuation call (unsound for inifinitely many continuations) + for (ACont(target, captured) <- fv.cont) { + val as0 = + as.map(v => if (cp.func.isReturnComp) v.wrapCompletion else v) + val newLocals = getLocals( + CallPoint(callerNp, target), + as0, + cont = true, + method = false, + ) ++ captured + sem += target -> st.copied(locals = newLocals) + } + AbsValue.Bot } - } - case ELexical(name, expr) => notSupported("ELexical") - case e @ EMap(tname, props) => - val asite = AllocSite(e.asite, cp.view) - for { - pairs <- join(props.map { - case (kexpr, vexpr) => + case IMethodCall(_, base, method, args) => + for { + rv <- transfer(base) + bv <- transfer(rv) + // TODO do not explicitly store methods in object but use a type + // model when accessing methods + fv <- get(_.get(bv, AbsValue(Str(method)))) + as <- join(args.map(transfer)) + st <- get + } yield { + for (AClo(func, _) <- fv.clo) + doCall( + callerNp, + st, + func, + bv.refineThis(func) :: as, + method = true, + ) + AbsValue.Bot + } + case ISdoCall(_, base, method, args) => + for { + bv <- transfer(base) + as <- join(args.map(transfer)) + st <- get + } yield { + var newV: AbsValue = AbsValue.Bot + bv.getSingle match + case One(AstValue(syn: Syntactic)) => + getSDO((syn, method)) match + case Some((ast0, sdo)) => + doCall( + callerNp, + st, + sdo, + AbsValue(ast0) :: as, + method = true, + ) + case None => error("invalid sdo") + case One(AstValue(lex: Lexical)) => + newV ⊔= AbsValue(Interpreter.eval(lex, method)) + case Many => + // lexical sdo + newV ⊔= bv.getLexical(method) + + // syntactic sdo + for ((sdo, ast) <- bv.getSDO(method)) + doCall(callerNp, st, sdo, ast :: as, method = true) + case _ => /* do nothing */ + newV + } + } + + /** transfer function for expressions */ + def transfer(expr: Expr)(using cp: ControlPoint): Result[AbsValue] = + expr match { + case EComp(ty, value, target) => + for { + tyV <- transfer(ty) + v <- transfer(value) + targetV <- transfer(target) + } yield AbsValue.createCompletion(tyV, v, targetV) + case EIsCompletion(expr) => + for { + v <- transfer(expr) + } yield v.isCompletion + case riaExpr @ EReturnIfAbrupt(ERef(ref), check) => + for { + rv <- transfer(ref) + v <- transfer(rv) + newV <- returnIfAbrupt(riaExpr, v, check) + _ <- + if (!newV.isBottom) modify(_.update(rv, newV)) + else put(AbsState.Bot) + } yield newV + case riaExpr @ EReturnIfAbrupt(expr, check) => + for { + v <- transfer(expr) + newV <- returnIfAbrupt(riaExpr, v, check) + } yield newV + case EPop(list, front) => + for { + v <- transfer(list) + pv <- id(_.pop(v, front)) + } yield pv + case EParse(code, rule) => + for { + c <- transfer(code) + r <- transfer(rule) + } yield c.parse(r) + case ENt(name, params) => AbsValue(Nt(name, params)) + case ESourceText(expr) => + for { v <- transfer(expr) } yield v.sourceText + case e @ EGetChildren(ast) => + val asite = AllocSite(e.asite, cp.view) + for { + av <- transfer(ast) + lv <- id(_.getChildren(asite, av)) + } yield lv + case e @ EGetItems(nt, ast) => + val asite = AllocSite(e.asite, cp.view) + for { + nv <- transfer(nt) + av <- transfer(ast) + lv <- id(_.getItems(asite, nv, av)) + } yield lv + case EYet(msg) => + if (yetThrow) notSupported(msg) + else AbsValue.Bot + case EContains(list, elem) => + for { + l <- transfer(list) + v <- transfer(elem) + st <- get + } yield st.contains(l, v) + case ESubstring(expr, from, None) => + for { + v <- transfer(expr) + f <- transfer(from) + } yield v.substring(f) + case ESubstring(expr, from, Some(to)) => + for { + v <- transfer(expr) + f <- transfer(from) + t <- transfer(to) + } yield v.substring(f, t) + case ETrim(expr, leading, trailing) => + for { + v <- transfer(expr) + } yield v.trim(leading, trailing) + case ERef(ref) => + for { + rv <- transfer(ref) + v <- transfer(rv) + } yield v + case EUnary(uop, expr) => + for { + v <- transfer(expr) + v0 <- get(transfer(_, uop, v)) + } yield v0 + case EBinary(BOp.And, left, right) => + shortCircuit(BOp.And, left, right) + case EBinary(BOp.Or, left, right) => shortCircuit(BOp.Or, left, right) + case EBinary(BOp.Eq, ERef(ref), EAbsent()) => + for { + rv <- transfer(ref) + b <- get(_.exists(rv)) + } yield !b + case EBinary(bop, left, right) => + for { + lv <- transfer(left) + rv <- transfer(right) + v <- get(transfer(_, bop, lv, rv)) + } yield v + case EVariadic(vop, exprs) => + for { + vs <- join(exprs.map(transfer)) + } yield transfer(vop, vs) + case EClamp(target, lower, upper) => + for { + v <- transfer(target) + lv <- transfer(lower) + uv <- transfer(upper) + } yield v.clamp(lv, uv) + case EMathOp(mop, exprs) => + for { + vs <- join(exprs.map(transfer)) + } yield transfer(mop, vs) + case EConvert(cop, expr) => + import COp.* + for { + v <- transfer(expr) + r <- cop match + case ToStr(Some(radix)) => transfer(radix) + case ToStr(None) => pure(AbsValue(Math(10))) + case _ => pure(AbsValue.Bot) + } yield v.convertTo(cop, r) + case ETypeOf(base) => + for { + v <- transfer(base) + st <- get + } yield v.typeOf(st) + case ETypeCheck(expr, tyExpr) => + for { + v <- transfer(expr) + tv <- transfer(tyExpr) + st <- get + } yield tv.getSingle match + case One(Str(s)) => v.typeCheck(s, st) + case One(Nt(n, _)) => v.typeCheck(n, st) + case _ => AbsValue.boolTop + + case EClo(fname, cap) => + cfg.fnameMap.get(fname) match { + case Some(f) => for { - k <- transfer(kexpr) - v <- transfer(vexpr) - } yield (k, v) - }) - lv <- id(_.allocMap(asite, tname, pairs)) - } yield lv - case e @ EList(exprs) => - val asite = AllocSite(e.asite, cp.view) - for { - vs <- join(exprs.map(transfer)) - lv <- id(_.allocList(asite, vs)) - } yield lv - case e @ EListConcat(exprs) => - val asite = AllocSite(e.asite, cp.view) - for { - ls <- join(exprs.map(transfer)) - lv <- id(_.concat(asite, ls)) - } yield lv - case e @ ESymbol(desc) => - val asite = AllocSite(e.asite, cp.view) - for { - v <- transfer(desc) - lv <- id(_.allocSymbol(asite, v)) - } yield lv - case e @ ECopy(obj) => - val asite = AllocSite(e.asite, cp.view) - for { - v <- transfer(obj) - lv <- id(_.copyObj(asite, v)) - } yield lv - case e @ EKeys(map, intSorted) => - val asite = AllocSite(e.asite, cp.view) - for { - v <- transfer(map) - lv <- id(_.keys(asite, v, intSorted)) - } yield lv - case EDuplicated(expr) => - for { - v <- transfer(expr) - st <- get - } yield v.duplicated(st) - case EIsArrayIndex(expr) => - for { - v <- transfer(expr) - } yield v.isArrayIndex - case EMathVal(n) => AbsValue(Math(n)) - case ENumber(n) if n.isNaN => AbsValue(Double.NaN) - case ENumber(n) => AbsValue(n) - case EBigInt(n) => AbsValue(BigInt(n)) - case EStr(str) => AbsValue(Str(str)) - case EBool(b) => AbsValue(Bool(b)) - case EUndef() => AbsValue(Undef) - case ENull() => AbsValue(Null) - case EAbsent() => AbsValue(Absent) - case EConst(name) => AbsValue(Const(name)) - case ECodeUnit(c) => AbsValue(CodeUnit(c)) - } + st <- get + captured = cap.map(x => x -> st.lookupLocal(x)).toMap + } yield AbsValue(AClo(f, captured)) + case None => + for { _ <- put(AbsState.Bot) } yield AbsValue.Bot + } + case ECont(fname) => + for { + st <- get + func = cfg.fnameMap(fname) + target = NodePoint(func, func.entry, cp.view) + captured = st.locals.collect { case (x: Name, av) => x -> av } + // return edges for resumed evaluation + currRp = ReturnPoint(cp.func, cp.view) + contRp = ReturnPoint(func, cp.view) + _ = sem.retEdges += (contRp -> sem.retEdges.getOrElse( + currRp, + Set(), + )) + } yield AbsValue(ACont(target, captured)) + case ERandom() => pure(AbsValue.numberTop) + case ESyntactic(name, args, rhsIdx, children) => + for { + cs <- join(children.map { + case Some(child) => transfer(child).map(Some(_)) + case None => pure(None) + }) + } yield { + if (cs.exists(cOpt => cOpt.fold(false)(_.isBottom))) AbsValue.Bot + else { + val cs0 = cs.map(cOpt => + cOpt.map(_.getSingle match { + case One(AstValue(child)) => child + case _ => exploded("ESyntactic") + }), + ) + AbsValue(Syntactic(name, args, rhsIdx, cs0)) + } + } + case ELexical(name, expr) => notSupported("ELexical") + case e @ EMap(tname, props) => + val asite = AllocSite(e.asite, cp.view) + for { + pairs <- join(props.map { + case (kexpr, vexpr) => + for { + k <- transfer(kexpr) + v <- transfer(vexpr) + } yield (k, v) + }) + lv <- id(_.allocMap(asite, tname, pairs)) + } yield lv + case e @ EList(exprs) => + val asite = AllocSite(e.asite, cp.view) + for { + vs <- join(exprs.map(transfer)) + lv <- id(_.allocList(asite, vs)) + } yield lv + case e @ EListConcat(exprs) => + val asite = AllocSite(e.asite, cp.view) + for { + ls <- join(exprs.map(transfer)) + lv <- id(_.concat(asite, ls)) + } yield lv + case e @ ESymbol(desc) => + val asite = AllocSite(e.asite, cp.view) + for { + v <- transfer(desc) + lv <- id(_.allocSymbol(asite, v)) + } yield lv + case e @ ECopy(obj) => + val asite = AllocSite(e.asite, cp.view) + for { + v <- transfer(obj) + lv <- id(_.copyObj(asite, v)) + } yield lv + case e @ EKeys(map, intSorted) => + val asite = AllocSite(e.asite, cp.view) + for { + v <- transfer(map) + lv <- id(_.keys(asite, v, intSorted)) + } yield lv + case EDuplicated(expr) => + for { + v <- transfer(expr) + st <- get + } yield v.duplicated(st) + case EIsArrayIndex(expr) => + for { + v <- transfer(expr) + } yield v.isArrayIndex + case EMathVal(n) => AbsValue(Math(n)) + case ENumber(n) if n.isNaN => AbsValue(Double.NaN) + case ENumber(n) => AbsValue(n) + case EBigInt(n) => AbsValue(BigInt(n)) + case EStr(str) => AbsValue(Str(str)) + case EBool(b) => AbsValue(Bool(b)) + case EUndef() => AbsValue(Undef) + case ENull() => AbsValue(Null) + case EAbsent() => AbsValue(Absent) + case EConst(name) => AbsValue(Const(name)) + case ECodeUnit(c) => AbsValue(CodeUnit(c)) + } - /** transfer function for references */ - def transfer(ref: Ref)(using cp: ControlPoint): Result[AbsRefValue] = - ref match - case id: Id => AbsRefId(id) - case Prop(ref, expr) => - for { - rv <- transfer(ref) - b <- transfer(rv) - p <- transfer(expr) - } yield AbsRefProp(b, p) - - /** transfer function for reference values */ - def transfer(rv: AbsRefValue)(using cp: ControlPoint): Result[AbsValue] = - for { v <- get(_.get(rv, cp)) } yield v - - /** transfer function for unary operators */ - def transfer( - st: AbsState, - uop: UOp, - operand: AbsValue, - )(using cp: ControlPoint): AbsValue = - import UOp.* - operand.getSingle match - case Zero => AbsValue.Bot - case One(x: SimpleValue) => - optional(AbsValue(Interpreter.eval(uop, x))).getOrElse(AbsValue.Bot) - case One(Math(x)) => - optional(AbsValue(Interpreter.eval(uop, Math(x)))) - .getOrElse(AbsValue.Bot) - case One(_) => AbsValue.Bot - case Many => - uop match - case Neg => -operand - case Not => !operand - case BNot => ~operand - case Abs => operand.abs - case Floor => operand.floor - - /** transfer function for binary operators */ - def transfer( - st: AbsState, - bop: BOp, - left: AbsValue, - right: AbsValue, - )(using cp: ControlPoint): AbsValue = - import BOp.* - (left.getSingle, right.getSingle) match { - case (Zero, _) | (_, Zero) => AbsValue.Bot - case (One(l: SimpleValue), One(r: SimpleValue)) => - optional(AbsValue(Interpreter.eval(bop, l, r))) - .getOrElse(AbsValue.Bot) - case (One(Math(l)), One(Math(r))) => - optional(AbsValue(Interpreter.eval(bop, Math(l), Math(r)))) - .getOrElse(AbsValue.Bot) - case (One(lpart: Part), One(rpart: Part)) if bop == Eq || bop == Equal => - if (lpart == rpart) { - if (st.isSingle(lpart)) AVT - else AVB - } else AVF - case (One(l), One(r)) if bop == Eq || bop == Equal => - AbsValue(l == r) - case _ => - bop match { - case BAnd => left & right - case BOr => left | right - case BXOr => left ^ right - case Eq => left =^= right - case Equal => left ==^== right - case Lt => left < right - case And => left && right - case Or => left || right - case Xor => left ^^ right - case Add => left + right - case Sub => left sub right - case Div => left / right - case Mul => left * right - case Mod => left % right - case UMod => left %% right - case Pow => left ** right - case LShift => left << right - case SRShift => left >> right - case URShift => left >>> right - } + /** transfer function for references */ + def transfer(ref: Ref)(using cp: ControlPoint): Result[AbsRefValue] = + ref match + case id: Id => AbsRefId(id) + case Prop(ref, expr) => + for { + rv <- transfer(ref) + b <- transfer(rv) + p <- transfer(expr) + } yield AbsRefProp(b, p) + + /** transfer function for reference values */ + def transfer(rv: AbsRefValue)(using cp: ControlPoint): Result[AbsValue] = + for { v <- get(_.get(rv, cp)) } yield v + + /** transfer function for unary operators */ + def transfer( + st: AbsState, + uop: UOp, + operand: AbsValue, + )(using cp: ControlPoint): AbsValue = + import UOp.* + operand.getSingle match + case Zero => AbsValue.Bot + case One(x: SimpleValue) => + optional(AbsValue(Interpreter.eval(uop, x))).getOrElse(AbsValue.Bot) + case One(Math(x)) => + optional(AbsValue(Interpreter.eval(uop, Math(x)))) + .getOrElse(AbsValue.Bot) + case One(_) => AbsValue.Bot + case Many => + uop match + case Neg => -operand + case Not => !operand + case BNot => ~operand + case Abs => operand.abs + case Floor => operand.floor + + /** transfer function for binary operators */ + def transfer( + st: AbsState, + bop: BOp, + left: AbsValue, + right: AbsValue, + )(using cp: ControlPoint): AbsValue = + import BOp.* + (left.getSingle, right.getSingle) match { + case (Zero, _) | (_, Zero) => AbsValue.Bot + case (One(l: SimpleValue), One(r: SimpleValue)) => + optional(AbsValue(Interpreter.eval(bop, l, r))) + .getOrElse(AbsValue.Bot) + case (One(Math(l)), One(Math(r))) => + optional(AbsValue(Interpreter.eval(bop, Math(l), Math(r)))) + .getOrElse(AbsValue.Bot) + case (One(lpart: Part), One(rpart: Part)) + if bop == Eq || bop == Equal => + if (lpart == rpart) { + if (st.isSingle(lpart)) AVT + else AVB + } else AVF + case (One(l), One(r)) if bop == Eq || bop == Equal => + AbsValue(l == r) + case _ => + bop match { + case BAnd => left & right + case BOr => left | right + case BXOr => left ^ right + case Eq => left =^= right + case Equal => left ==^== right + case Lt => left < right + case And => left && right + case Or => left || right + case Xor => left ^^ right + case Add => left + right + case Sub => left sub right + case Div => left / right + case Mul => left * right + case Mod => left % right + case UMod => left %% right + case Pow => left ** right + case LShift => left << right + case SRShift => left >> right + case URShift => left >>> right + } + } + + /** transfer for variadic operators */ + def transfer(vop: VOp, vs: List[AbsValue])(using + cp: ControlPoint, + ): AbsValue = + AbsValue.vopTransfer(vop, vs) + + /** transfer for mathematical operators */ + def transfer(mop: MOp, vs: List[AbsValue])(using + cp: ControlPoint, + ): AbsValue = + AbsValue.mopTransfer(mop, vs) + + /** handle calls */ + def doCall( + callerNp: NodePoint[Call], + callerSt: AbsState, + calleeFunc: Func, + args: List[AbsValue], + captured: Map[Name, AbsValue] = Map(), + method: Boolean = false, + ): Unit = + sem.callInfo += callerNp -> callerSt + for { + (calleeNp, calleeSt) <- getCalleeEntries( + callerNp, + callerSt, + calleeFunc, + args, + captured, + method, + ) + } { + // add callee to worklist + sem += calleeNp -> calleeSt.doCall + // add return edges from callee to caller + val rp = ReturnPoint(calleeFunc, calleeNp.view) + val set = sem.getRetEdges(rp) + sem.retEdges += rp -> (set + callerNp) + // propagate callee analysis result + val retT = sem(rp) + if (!retT.isBottom) sem.worklist += rp + } + + // return specific value + def doReturn( + irReturn: Return, + v: AbsValue, + )(using cp: ControlPoint): Result[Unit] = for { + st <- get + ret = AbsRet(v, st.copied(locals = Map())) + calleeRp = ReturnPoint(cp.func, cp.view) + irp = InternalReturnPoint(irReturn, calleeRp) + _ = doReturn(irp, ret) + } yield () + + /** update return points */ + def doReturn(irp: InternalReturnPoint, givenRet: AbsRet): Unit = + val InternalReturnPoint(_, ReturnPoint(func, view)) = irp + val retRp = ReturnPoint(func, sem.getEntryView(view)) + // wrap completion by conditions specified in + // [5.2.3.5 Implicit Normal Completion] + // (https://tc39.es/ecma262/#sec-implicit-normal-completion) + val newRet = if (func.isReturnComp) givenRet.wrapCompletion else givenRet + if (!newRet.value.isBottom) + val oldRet = sem(retRp) + if (!oldRet.isBottom && useRepl) Repl.merged = true + if (newRet !⊑ oldRet) + sem.rpMap += retRp -> (oldRet ⊔ newRet) + sem.worklist += retRp + + /** return-if-abrupt completion */ + def returnIfAbrupt( + riaExpr: EReturnIfAbrupt, + value: AbsValue, + check: Boolean, + )(using cp: ControlPoint): Result[AbsValue] = { + val checkReturn: Result[Unit] = + if (check) doReturn(riaExpr, value.abruptCompletion) + else () + for (_ <- checkReturn) yield value.unwrapCompletion } - /** transfer for variadic operators */ - def transfer(vop: VOp, vs: List[AbsValue])(using cp: ControlPoint): AbsValue = - AbsValue.vopTransfer(vop, vs) - - /** transfer for mathematical operators */ - def transfer(mop: MOp, vs: List[AbsValue])(using cp: ControlPoint): AbsValue = - AbsValue.mopTransfer(mop, vs) - - /** handle calls */ - def doCall( - callerNp: NodePoint[Call], - callerSt: AbsState, - calleeFunc: Func, - args: List[AbsValue], - captured: Map[Name, AbsValue] = Map(), - method: Boolean = false, - ): Unit = - sem.callInfo += callerNp -> callerSt - for { - (calleeNp, calleeSt) <- getCalleeEntries( - callerNp, - callerSt, - calleeFunc, - args, - captured, - method, + // short circuit evaluation + def shortCircuit( + bop: BOp, + left: Expr, + right: Expr, + )(using cp: ControlPoint): Result[AbsValue] = for { + l <- transfer(left) + v <- (bop, l.getSingle) match { + case (BOp.And, One(Bool(false))) => pure(AVF) + case (BOp.Or, One(Bool(true))) => pure(AVT) + case _ => + for { + r <- transfer(right) + v <- get(transfer(_, bop, l, r)) + } yield v + } + } yield v + + /** call transition */ + def getCalleeEntries( + callerNp: NodePoint[Call], + callerSt: AbsState, + calleeFunc: Func, + args: List[AbsValue], + captured: Map[Name, AbsValue], + method: Boolean, + ): List[(NodePoint[_], AbsState)] = { + // handle ir callsite sensitivity + val NodePoint(callerFunc, callSite, callerView) = callerNp + val baseView = + if (irSens) + callerView.copy( + calls = callSite :: callerView.calls, + intraLoopDepth = 0, + ) + else callerView + + val calleeNp = NodePoint(calleeFunc, calleeFunc.entry, baseView) + val calleeSt = callerSt.copied(locals = + getLocals( + CallPoint(callerNp, calleeNp), + args, + cont = false, + method, + ) ++ captured, ) - } { - // add callee to worklist - sem += calleeNp -> calleeSt.doCall - // add return edges from callee to caller - val rp = ReturnPoint(calleeFunc, calleeNp.view) - val set = sem.getRetEdges(rp) - sem.retEdges += rp -> (set + callerNp) - // propagate callee analysis result - val retT = sem(rp) - if (!retT.isBottom) sem.worklist += rp + List((calleeNp, calleeSt)) } - // return specific value - def doReturn( - irReturn: Return, - v: AbsValue, - )(using cp: ControlPoint): Result[Unit] = for { - st <- get - ret = AbsRet(v, st.copied(locals = Map())) - calleeRp = ReturnPoint(cp.func, cp.view) - irp = InternalReturnPoint(irReturn, calleeRp) - _ = doReturn(irp, ret) - } yield () - - /** update return points */ - def doReturn(irp: InternalReturnPoint, givenRet: AbsRet): Unit = - val InternalReturnPoint(_, ReturnPoint(func, view)) = irp - val retRp = ReturnPoint(func, sem.getEntryView(view)) - // wrap completion by conditions specified in - // [5.2.3.5 Implicit Normal Completion] - // (https://tc39.es/ecma262/#sec-implicit-normal-completion) - val newRet = if (func.isReturnComp) givenRet.wrapCompletion else givenRet - if (!newRet.value.isBottom) - val oldRet = sem(retRp) - if (!oldRet.isBottom && USE_REPL) REPL.merged = true - if (newRet !⊑ oldRet) - sem.rpMap += retRp -> (oldRet ⊔ newRet) - sem.worklist += retRp - - /** return-if-abrupt completion */ - def returnIfAbrupt( - riaExpr: EReturnIfAbrupt, - value: AbsValue, - check: Boolean, - )(using cp: ControlPoint): Result[AbsValue] = { - val checkReturn: Result[Unit] = - if (check) doReturn(riaExpr, value.abruptCompletion) - else () - for (_ <- checkReturn) yield value.unwrapCompletion - } - - // short circuit evaluation - def shortCircuit( - bop: BOp, - left: Expr, - right: Expr, - )(using cp: ControlPoint): Result[AbsValue] = for { - l <- transfer(left) - v <- (bop, l.getSingle) match { - case (BOp.And, One(Bool(false))) => pure(AVF) - case (BOp.Or, One(Bool(true))) => pure(AVT) - case _ => - for { - r <- transfer(right) - v <- get(transfer(_, bop, l, r)) - } yield v + /** get local variables */ + def getLocals( + cp: CallPoint[Node], + args: List[AbsValue], + cont: Boolean, + method: Boolean, + ): Map[Local, AbsValue] = { + val CallPoint(callerNp, calleeNp) = cp + val params: List[Param] = calleeNp.func.irFunc.params + var map = Map[Local, AbsValue]() + + @tailrec + def aux(ps: List[Param], as: List[AbsValue]): Unit = (ps, as) match { + case (Nil, Nil) => + case (Param(lhs, _, optional, _) :: pl, Nil) => + if (optional) { + map += lhs -> AbsValue(Absent) + aux(pl, Nil) + } + case (Nil, args) => + // XXX Handle GeneratorStart <-> GeneratorResume arith mismatch + case (param :: pl, arg :: al) => + map += param.lhs -> arg + aux(pl, al) + } + aux(params, args) + map } - } yield v - - /** call transition */ - def getCalleeEntries( - callerNp: NodePoint[Call], - callerSt: AbsState, - calleeFunc: Func, - args: List[AbsValue], - captured: Map[Name, AbsValue], - method: Boolean, - ): List[(NodePoint[_], AbsState)] = { - // handle ir callsite sensitivity - val NodePoint(callerFunc, callSite, callerView) = callerNp - val baseView = - if (IR_SENS) - callerView.copy( - calls = callSite :: callerView.calls, - intraLoopDepth = 0, - ) - else callerView - - val calleeNp = NodePoint(calleeFunc, calleeFunc.entry, baseView) - val calleeSt = callerSt.copied(locals = - getLocals( - CallPoint(callerNp, calleeNp), - args, - cont = false, - method, - ) ++ captured, - ) - List((calleeNp, calleeSt)) - } - /** get local variables */ - def getLocals( - cp: CallPoint[Node], - args: List[AbsValue], - cont: Boolean, - method: Boolean, - ): Map[Local, AbsValue] = { - val CallPoint(callerNp, calleeNp) = cp - val params: List[Param] = calleeNp.func.irFunc.params - var map = Map[Local, AbsValue]() + object OptimizedCall { + def unapply(callInst: CallInst)(using + cp: NodePoint[_], + ): Option[Result[AbsValue]] = callInst match + case ICall(_, EClo("Completion", Nil), List(expr)) => + Some(for { + v <- transfer(expr) + _ <- modify(prune(ETypeCheck(expr, EStr("CompletionRecord")), true)) + } yield v) + case ICall(_, EClo("NormalCompletion", Nil), List(expr)) => + Some(transfer(expr).map(_.normalCompletion)) + case ICall(_, EClo("UpdateEmpty", Nil), List(compExpr, valueExpr)) => + AbsValue match + case ValueBasicDomain => + Some(for { + compValue <- transfer(compExpr) + newValue <- transfer(valueExpr) + } yield { + val AbsComp(map) = compValue.comp + val empty = AbsPureValue(CONST_EMPTY) + val newMap = map.map { + case (ty, res @ AbsComp.Result(value, target)) => + ty -> ( + if (empty !⊑ value) res + else + res.copy(value = (value -- empty) ⊔ newValue.pureValue) + ) + } + AbsValue(AbsComp(newMap)) + }) + case ValueTypeDomain => + Some(for { + compValue <- transfer(compExpr) + newValue <- transfer(valueExpr) + } yield { + val compTy = compValue.ty.comp + val normalTy = compTy.normal + val newValueTy = newValue.ty.pureValue + val emptyTy = ConstT("empty").pureValue + val updated = + compTy.copy(normal = (normalTy -- emptyTy) ⊔ newValueTy) + AbsValue(ValueTy(comp = updated)) + }) + case _ => None + case _ => None + } - @tailrec - def aux(ps: List[Param], as: List[AbsValue]): Unit = (ps, as) match { - case (Nil, Nil) => - case (Param(lhs, _, optional, _) :: pl, Nil) => - if (optional) { - map += lhs -> AbsValue(Absent) - aux(pl, Nil) - } - case (Nil, args) => - // XXX Handle GeneratorStart <-> GeneratorResume arith mismatch - case (param :: pl, arg :: al) => - map += param.lhs -> arg - aux(pl, al) + /** prune condition */ + def prune( + cond: Expr, + positive: Boolean, + )(using cp: NodePoint[_]): Updater = cond match { + case _ if !useRefine => st => st + // prune values + case EBinary(BOp.Eq, ERef(ref: Local), target) => + for { + l <- transfer(ref) + r <- transfer(target) + lv <- transfer(l) + prunedV = lv.pruneValue(r, positive) + _ <- modify(_.update(l, prunedV)) + } yield () + // prune fields + case EBinary(BOp.Eq, ERef(Prop(ref: Local, EStr(field))), target) => + for { + l <- transfer(ref) + r <- transfer(target) + lv <- transfer(l) + prunedV = lv.pruneField(field, r, positive) + _ <- modify(_.update(l, prunedV)) + } yield () + // prune types + case EBinary(BOp.Eq, ETypeOf(ERef(ref: Local)), tyRef: ERef) => + for { + l <- transfer(ref) + r <- transfer(tyRef) + lv <- transfer(l) + prunedV = lv.pruneType(r, positive) + _ <- modify(_.update(l, prunedV)) + } yield () + // prune type checks + case ETypeCheck(ERef(ref: Local), tyExpr) => + for { + l <- transfer(ref) + r <- transfer(tyExpr) + lv <- transfer(l) + prunedV = lv.pruneTypeCheck(r, positive) + _ <- modify(_.update(l, prunedV)) + } yield () + case EUnary(UOp.Not, e) => prune(e, !positive) + case EBinary(BOp.Or, l, r) => + st => + lazy val ltst = prune(l, true)(st) + lazy val lfst = prune(l, false)(st) + val rst = prune(r, positive)(lfst) + if (positive) ltst ⊔ rst else lfst ⊓ rst + case EBinary(BOp.And, l, r) => + st => + lazy val ltst = prune(l, true)(st) + lazy val lfst = prune(l, false)(st) + val rst = prune(r, positive)(ltst) + if (positive) ltst ⊓ rst else lfst ⊔ rst + case _ => st => st } - aux(params, args) - map } } diff --git a/src/main/scala/esmeta/analyzer/AnalysisPoint.scala b/src/main/scala/esmeta/analyzer/AnalysisPoint.scala index 51a288ab2b..f1ef98c67a 100644 --- a/src/main/scala/esmeta/analyzer/AnalysisPoint.scala +++ b/src/main/scala/esmeta/analyzer/AnalysisPoint.scala @@ -3,63 +3,66 @@ package esmeta.analyzer import esmeta.ir.{Func => _, *} import esmeta.cfg.* -/** analysis points */ -sealed trait AnalysisPoint extends AnalyzerElem { - def view: View - def func: Func - def isBuiltin: Boolean = func.isBuiltin - def toReturnPoint: ReturnPoint = ReturnPoint(func, view) -} +trait AnalysisPointDecl { self: Analyzer => -/** call points */ -case class CallPoint[+T <: Node]( - callerNp: NodePoint[Call], - calleeNp: NodePoint[T], -) extends AnalysisPoint { - inline def view = calleeNp.view - inline def func = calleeNp.func -} + /** analysis points */ + sealed trait AnalysisPoint extends AnalyzerElem { + def view: View + def func: Func + def isBuiltin: Boolean = func.isBuiltin + def toReturnPoint: ReturnPoint = ReturnPoint(func, view) + } -/** argument assignment points */ -case class ArgAssignPoint[+T <: Node]( - cp: CallPoint[T], - idx: Int, -) extends AnalysisPoint { - inline def view = cp.view - inline def func = cp.func - inline def param = cp.calleeNp.func.params(idx) -} + /** call points */ + case class CallPoint[+T <: Node]( + callerNp: NodePoint[Call], + calleeNp: NodePoint[T], + ) extends AnalysisPoint { + inline def view = calleeNp.view + inline def func = calleeNp.func + } -/** internal return points */ -case class InternalReturnPoint( - irReturn: Return, - calleeRp: ReturnPoint, -) extends AnalysisPoint { - inline def view = calleeRp.view - inline def func = calleeRp.func -} + /** argument assignment points */ + case class ArgAssignPoint[+T <: Node]( + cp: CallPoint[T], + idx: Int, + ) extends AnalysisPoint { + inline def view = cp.view + inline def func = cp.func + inline def param = cp.calleeNp.func.params(idx) + } -/** return-if-abrupt points */ -case class ReturnIfAbruptPoint( - cp: ControlPoint, - riaExpr: EReturnIfAbrupt, -) extends AnalysisPoint { - inline def view = cp.view - inline def func = cp.func -} + /** internal return points */ + case class InternalReturnPoint( + irReturn: Return, + calleeRp: ReturnPoint, + ) extends AnalysisPoint { + inline def view = calleeRp.view + inline def func = calleeRp.func + } + + /** return-if-abrupt points */ + case class ReturnIfAbruptPoint( + cp: ControlPoint, + riaExpr: EReturnIfAbrupt, + ) extends AnalysisPoint { + inline def view = cp.view + inline def func = cp.func + } -/** control points */ -sealed trait ControlPoint extends AnalysisPoint + /** control points */ + sealed trait ControlPoint extends AnalysisPoint -/** node points */ -case class NodePoint[+T <: Node]( - func: Func, - node: T, - view: View, -) extends ControlPoint + /** node points */ + case class NodePoint[+T <: Node]( + func: Func, + node: T, + view: View, + ) extends ControlPoint -/** return points */ -case class ReturnPoint( - func: Func, - view: View, -) extends ControlPoint + /** return points */ + case class ReturnPoint( + func: Func, + view: View, + ) extends ControlPoint +} diff --git a/src/main/scala/esmeta/analyzer/Analyzer.scala b/src/main/scala/esmeta/analyzer/Analyzer.scala index c119bb4202..fcadfb57ec 100644 --- a/src/main/scala/esmeta/analyzer/Analyzer.scala +++ b/src/main/scala/esmeta/analyzer/Analyzer.scala @@ -1,28 +1,186 @@ package esmeta.analyzer -import esmeta.cfg.* +import esmeta.analyzer.util.* +import esmeta.cfg.{util => _, *} +import esmeta.state.{util => _, *} +import esmeta.util.BaseUtils.* /** static analyzer */ -abstract class Analyzer(val cfg: CFG) { +abstract class Analyzer + extends AbsTransferDecl + with AbsSemanticsDecl + with AnalysisPointDecl + with TypeMismatchDecl + with ViewDecl + with domain.Decl + with repl.Decl + with util.Decl { - /** specific shapes of abstract semantics */ + /** control flow graph */ + val cfg: CFG + + /** abstract semantics */ type Semantics <: AbsSemantics + lazy val sem: Semantics - /** specific abstract transfer function as transfer function */ + /** transfer function */ type Transfer <: AbsTransfer + lazy val transfer: Transfer - /** transfer function */ - val transfer: Transfer - - /** perform analysis for a given initial abstract semantics */ - def apply( - init: => Semantics, - postProcess: Semantics => Semantics = identity, - ): Semantics = withAnalyzer(this) { - val sem = init - withSem(sem) { - transfer.fixpoint - postProcess(sem) + /** analyzer elements */ + trait AnalyzerElem { + override def toString: String = toString(true, false, false) + + /** stringify with options */ + def toString( + detail: Boolean = false, + line: Boolean = false, + asite: Boolean = false, + ): String = + val stringifier = AnalyzerElem.getStringifier(detail, line, asite) + import stringifier.elemRule + stringify(this) + } + object AnalyzerElem { + val getStringifier = cached[(Boolean, Boolean, Boolean), Stringifier] { + Stringifier(_, _, _) } } + + // --------------------------------------------------------------------------- + // analysis options + // --------------------------------------------------------------------------- + /** analysis time limit */ + val timeLimit: Option[Long] = None + + /** debugging mode */ + val debug: Boolean = false + + /** REPL mode */ + val useRepl: Boolean = false + + /** Run continue command at startup when using repl */ + val replContinue: Boolean = false + + /** check period */ + val checkPeriod: Int = 10000 + + /** IR sensitivity */ + val irSens: Boolean = true + + /** throw exception for not yet compiled expressions */ + val yetThrow: Boolean = false + + /** use condition-based refinement */ + val useRefine: Boolean = false + + // --------------------------------------------------------------------------- + // shortcuts + // --------------------------------------------------------------------------- + lazy val AB = AbsBool.Top + lazy val AT = AbsBool(T) + lazy val AF = AbsBool(F) + lazy val AVT = AbsValue(T) + lazy val AVF = AbsValue(F) + lazy val AVB = AbsValue(T, F) + lazy val AV_TYPE = AbsValue(Str("Type")) + lazy val AV_VALUE = AbsValue(Str("Value")) + lazy val AV_TARGET = AbsValue(Str("Target")) + + // --------------------------------------------------------------------------- + // abstract domains + // --------------------------------------------------------------------------- + protected var stateDomain: Option[StateDomain] = None + protected var retDomain: Option[RetDomain] = None + protected var heapDomain: Option[HeapDomain] = None + protected var objDomain: Option[ObjDomain] = None + protected var valueDomain: Option[ValueDomain] = None + protected var compDomain: Option[CompDomain] = None + protected var pureValueDomain: Option[PureValueDomain] = None + protected var cloDomain: Option[CloDomain] = None + protected var contDomain: Option[ContDomain] = None + protected var partDomain: Option[PartDomain] = None + protected var astValueDomain: Option[AstValueDomain] = None + protected var ntDomain: Option[NtDomain] = None + protected var mathDomain: Option[MathDomain] = None + protected var codeUnitDomain: Option[CodeUnitDomain] = None + protected var constDomain: Option[ConstDomain] = None + protected var simpleValueDomain: Option[SimpleValueDomain] = None + protected var numberDomain: Option[NumberDomain] = None + protected var bigIntDomain: Option[BigIntDomain] = None + protected var strDomain: Option[StrDomain] = None + protected var boolDomain: Option[BoolDomain] = None + protected var undefDomain: Option[UndefDomain] = None + protected var nullDomain: Option[NullDomain] = None + protected var absentDomain: Option[AbsentDomain] = None + + final lazy val AbsState = stateDomain.getOrElse(StateBasicDomain) + type AbsState = AbsState.Elem + + final lazy val AbsRet = retDomain.getOrElse(RetBasicDomain) + type AbsRet = AbsRet.Elem + + final lazy val AbsHeap = heapDomain.getOrElse(HeapBasicDomain) + type AbsHeap = AbsHeap.Elem + + final lazy val AbsObj = objDomain.getOrElse(ObjBasicDomain) + type AbsObj = AbsObj.Elem + + final lazy val AbsValue = valueDomain.getOrElse(ValueBasicDomain) + type AbsValue = AbsValue.Elem + + final lazy val AbsComp = compDomain.getOrElse(CompBasicDomain) + type AbsComp = AbsComp.Elem + + final lazy val AbsPureValue = pureValueDomain.getOrElse(PureValueBasicDomain) + type AbsPureValue = AbsPureValue.Elem + + final lazy val AbsClo = cloDomain.getOrElse(CloSetDomain()) + type AbsClo = AbsClo.Elem + + final lazy val AbsCont = contDomain.getOrElse(ContSetDomain()) + type AbsCont = AbsCont.Elem + + final lazy val AbsPart = partDomain.getOrElse(PartSetDomain()) + type AbsPart = AbsPart.Elem + + final lazy val AbsAstValue = astValueDomain.getOrElse(AstValueFlatDomain) + type AbsAstValue = AbsAstValue.Elem + + final lazy val AbsNt = ntDomain.getOrElse(NtFlatDomain) + type AbsNt = AbsNt.Elem + + final lazy val AbsMath = mathDomain.getOrElse(MathFlatDomain) + type AbsMath = AbsMath.Elem + + final lazy val AbsCodeUnit = codeUnitDomain.getOrElse(CodeUnitFlatDomain) + type AbsCodeUnit = AbsCodeUnit.Elem + + final lazy val AbsConst = constDomain.getOrElse(ConstFlatDomain) + type AbsConst = AbsConst.Elem + + final lazy val AbsSimpleValue = + simpleValueDomain.getOrElse(SimpleValueBasicDomain) + type AbsSimpleValue = AbsSimpleValue.Elem + + final lazy val AbsNumber = numberDomain.getOrElse(NumberFlatDomain) + type AbsNumber = AbsNumber.Elem + + final lazy val AbsBigInt = bigIntDomain.getOrElse(BigIntFlatDomain) + type AbsBigInt = AbsBigInt.Elem + + final lazy val AbsStr = strDomain.getOrElse(StrSetDomain()) + type AbsStr = AbsStr.Elem + + final lazy val AbsBool = boolDomain.getOrElse(BoolFlatDomain) + type AbsBool = AbsBool.Elem + + final lazy val AbsUndef = undefDomain.getOrElse(UndefSimpleDomain) + type AbsUndef = AbsUndef.Elem + + final lazy val AbsNull = nullDomain.getOrElse(NullSimpleDomain) + type AbsNull = AbsNull.Elem + + final lazy val AbsAbsent = absentDomain.getOrElse(AbsentSimpleDomain) + type AbsAbsent = AbsAbsent.Elem } diff --git a/src/main/scala/esmeta/analyzer/ESAnalyzer.scala b/src/main/scala/esmeta/analyzer/ESAnalyzer.scala index 4684c37d97..c46f70efa4 100644 --- a/src/main/scala/esmeta/analyzer/ESAnalyzer.scala +++ b/src/main/scala/esmeta/analyzer/ESAnalyzer.scala @@ -6,20 +6,28 @@ import esmeta.es.* import esmeta.ir.* /** meta-level static analyzer for ECMAScript */ -class ESAnalyzer(cfg: CFG) extends Analyzer(cfg) { - - /** default abstract semantics */ +class ESAnalyzer( + val cfg: CFG, + override val useRepl: Boolean = false, +) extends Analyzer { + + /** perform type analysis with the given control flow graph */ + def apply(sourceText: String): Semantics = + AbsState.setBase(new Initialize(cfg)) + sem.reset(AbsSemantics(initNpMap(sourceText))) + transfer.fixpoint + sem + + /** abstract semantics */ + lazy val sem: AbsSemantics = new AbsSemantics type Semantics = AbsSemantics - /** default abstract transfer function as transfer */ - trait Transfer extends AbsTransfer - /** transfer function */ - object transfer extends Transfer + lazy val transfer: Transfer = new Transfer + class Transfer extends AbsTransfer - /** perform analysis for a given ECMAScript code */ - def apply(sourceText: String): AbsSemantics = - apply(AbsSemantics(initNpMap(sourceText))) + /** throw exception for not yet compiled expressions */ + override val yetThrow: Boolean = true // --------------------------------------------------------------------------- // private helpers @@ -39,6 +47,3 @@ class ESAnalyzer(cfg: CFG) extends Analyzer(cfg) { ), ) } -object ESAnalyzer: - // throw exceptions when touching not yet supported instructions - YET_THROW = true diff --git a/src/main/scala/esmeta/analyzer/Optimized.scala b/src/main/scala/esmeta/analyzer/Optimized.scala deleted file mode 100644 index 701875909a..0000000000 --- a/src/main/scala/esmeta/analyzer/Optimized.scala +++ /dev/null @@ -1,57 +0,0 @@ -package esmeta.analyzer - -import esmeta.state.CONST_EMPTY -import esmeta.ir.{Func => _, *} -import esmeta.ty.* - -trait Optimized { this: AbsTransfer => - - /** loading monads */ - import AbsState.monad.* - - object OptimizedCall { - def unapply(callInst: CallInst)(using - cp: NodePoint[_], - ): Option[Result[AbsValue]] = callInst match - case ICall(_, EClo("Completion", Nil), List(expr)) => - Some(for { - v <- transfer(expr) - _ <- modify(prune(ETypeCheck(expr, EStr("CompletionRecord")), true)) - } yield v) - case ICall(_, EClo("NormalCompletion", Nil), List(expr)) => - Some(transfer(expr).map(_.normalCompletion)) - case ICall(_, EClo("UpdateEmpty", Nil), List(compExpr, valueExpr)) => - AbsValue match - case domain.value.BasicDomain => - Some(for { - compValue <- transfer(compExpr) - newValue <- transfer(valueExpr) - } yield { - val AbsComp(map) = compValue.comp - val empty = AbsPureValue(CONST_EMPTY) - val newMap = map.map { - case (ty, res @ AbsComp.Result(value, target)) => - ty -> ( - if (empty !⊑ value) res - else res.copy(value = (value -- empty) ⊔ newValue.pureValue) - ) - } - AbsValue(AbsComp(newMap)) - }) - case domain.value.TypeDomain => - Some(for { - compValue <- transfer(compExpr) - newValue <- transfer(valueExpr) - } yield { - val compTy = compValue.ty.comp - val normalTy = compTy.normal - val newValueTy = newValue.ty.pureValue - val emptyTy = ConstT("empty").pureValue - val updated = - compTy.copy(normal = (normalTy -- emptyTy) ⊔ newValueTy) - AbsValue(ValueTy(comp = updated)) - }) - case _ => None - case _ => None - } -} diff --git a/src/main/scala/esmeta/analyzer/PruneHelper.scala b/src/main/scala/esmeta/analyzer/PruneHelper.scala deleted file mode 100644 index 10765383ff..0000000000 --- a/src/main/scala/esmeta/analyzer/PruneHelper.scala +++ /dev/null @@ -1,71 +0,0 @@ -package esmeta.analyzer - -import esmeta.analyzer.domain.* -import esmeta.analyzer.util.* -import esmeta.ir.{Func => _, *} -import esmeta.ty.* - -/** helper functions for pruning/refinement */ -trait PruneHelper { this: AbsTransfer => - - /** loading monads */ - import AbsState.monad.* - - /** prune condition */ - def prune( - cond: Expr, - positive: Boolean, - )(using cp: NodePoint[_]): Updater = cond match { - case _ if !USE_REFINE => st => st - // prune values - case EBinary(BOp.Eq, ERef(ref: Local), target) => - for { - l <- transfer(ref) - r <- transfer(target) - lv <- transfer(l) - prunedV = lv.pruneValue(r, positive) - _ <- modify(_.update(l, prunedV)) - } yield () - // prune fields - case EBinary(BOp.Eq, ERef(Prop(ref: Local, EStr(field))), target) => - for { - l <- transfer(ref) - r <- transfer(target) - lv <- transfer(l) - prunedV = lv.pruneField(field, r, positive) - _ <- modify(_.update(l, prunedV)) - } yield () - // prune types - case EBinary(BOp.Eq, ETypeOf(ERef(ref: Local)), tyRef: ERef) => - for { - l <- transfer(ref) - r <- transfer(tyRef) - lv <- transfer(l) - prunedV = lv.pruneType(r, positive) - _ <- modify(_.update(l, prunedV)) - } yield () - // prune type checks - case ETypeCheck(ERef(ref: Local), tyExpr) => - for { - l <- transfer(ref) - r <- transfer(tyExpr) - lv <- transfer(l) - prunedV = lv.pruneTypeCheck(r, positive) - _ <- modify(_.update(l, prunedV)) - } yield () - case EUnary(UOp.Not, e) => prune(e, !positive) - case EBinary(BOp.Or, l, r) => - st => - lazy val ltst = prune(l, true)(st) - lazy val lfst = prune(l, false)(st) - val rst = prune(r, positive)(lfst) - if (positive) ltst ⊔ rst else lfst ⊓ rst - case EBinary(BOp.And, l, r) => - st => - lazy val ltst = prune(l, true)(st) - lazy val lfst = prune(l, false)(st) - val rst = prune(r, positive)(ltst) - if (positive) ltst ⊓ rst else lfst ⊔ rst - case _ => st => st - } -} diff --git a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala index b06ca5a649..286fd278f6 100644 --- a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala +++ b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala @@ -4,6 +4,7 @@ import esmeta.{ANALYZE_LOG_DIR, LINE_SEP} import esmeta.analyzer.domain.* import esmeta.cfg.* import esmeta.error.* +import esmeta.es.* import esmeta.ir.{Func => IRFunc, *} import esmeta.ty.* import esmeta.ty.util.{Stringifier => TyStringifier} @@ -15,20 +16,51 @@ import esmeta.util.SystemUtils.* /** specification type analyzer for ECMA-262 */ class TypeAnalyzer( - cfg: CFG, - config: TypeAnalyzer.Config = TypeAnalyzer.Config(), -) extends Analyzer(cfg) { + val cfg: CFG, + val targetPattern: Option[String] = None, + val config: TypeAnalyzer.Config = TypeAnalyzer.Config(), + val ignore: TypeAnalyzer.Ignore = Ignore(), + val log: Boolean = false, + val silent: Boolean = false, + override val useRepl: Boolean = false, + override val replContinue: Boolean = false, +) extends Analyzer { import TypeAnalyzer.* - // record type mismatches + /** perform type analysis with the given control flow graph */ + lazy val result: Semantics = + AbsState.setBase(new Initialize(cfg)) + transfer.fixpoint + postProcess + sem + + /** all possible initial analysis target functions */ + def targetFuncs: List[Func] = + val allFuncs = cfg.funcs.filter(_.isParamTysDefined) + val funcs = targetPattern.fold(allFuncs)(pattern => { + val funcs = allFuncs.filter(f => pattern.r.matches(f.name)) + if (!silent && funcs.isEmpty) + warn(s"failed to find functions matched with the pattern `$pattern`.") + funcs + }) + if (!silent) println(s"- ${funcs.size} functions are initial targets.") + funcs + + /** record type mismatches */ def addMismatch(mismatch: TypeMismatch): Unit = mismatchMap += mismatch.ap -> mismatch lazy val mismatches: Set[TypeMismatch] = mismatchMap.values.toSet private var mismatchMap: Map[AnalysisPoint, TypeMismatch] = Map() + /** no sensitivity */ + override val irSens: Boolean = false + + /** use type refinement */ + override val useRefine: Boolean = true + /** type semantics as results */ - class Semantics(npMap: Map[NodePoint[Node], AbsState]) - extends AbsSemantics(npMap) { + lazy val sem: Semantics = new Semantics + class Semantics extends AbsSemantics(getInitNpMap(targetFuncs)) { /** type analysis result string */ def typesString: String = @@ -59,8 +91,9 @@ class TypeAnalyzer( (new Appender >> cfg.funcs.toList.sortBy(_.name)).toString } - /** abstract transfer function for types */ - trait Transfer extends AbsTransfer { + /** transfer function */ + lazy val transfer: Transfer = new Transfer + class Transfer extends AbsTransfer { /** loading monads */ import AbsState.monad.* @@ -159,107 +192,84 @@ class TypeAnalyzer( super.doReturn(irp, expected) } - /** transfer function */ - object transfer extends Transfer + /** use type abstract domains */ + stateDomain = Some(StateTypeDomain) + retDomain = Some(RetTypeDomain) + valueDomain = Some(ValueTypeDomain) - /** perform type analysis for given targets */ - def apply( - targets: List[Func], - ignore: Ignore, - log: Boolean, - silent: Boolean, - ): Semantics = apply( - init = Semantics(initNpMap(targets)), - postProcess = (sem: Semantics) => { - if (log) logging(sem) - var unusedSet = ignore.names - val detected = mismatches.filter(mismatch => { - val name = mismatch.func.name - unusedSet -= name - !ignore.names.contains(name) - }) - if (!detected.isEmpty || !unusedSet.isEmpty) - val app = new Appender - // show detected type mismatches - if (!detected.isEmpty) - app :> "* " >> detected.size - app >> " type mismatches are detected." - // show unused names - if (!unusedSet.isEmpty) - app :> "* " >> unusedSet.size - app >> " names are not used to ignore mismatches." - detected.toList.map(_.toString).sorted.map(app :> _) - // show help message about how to use the ignorance system - ignore.filename.map(path => - if (ignore.update) - dumpJson( - name = "algorithm names for the ignorance system", - data = mismatches.map(_.func.name).toList.sorted, - filename = path, - noSpace = false, - ) - else - app :> "=" * 80 - app :> "To suppress this error message, " - app >> "add/remove the following names to `" >> path >> "`:" - detected.map(_.func.name).toList.sorted.map(app :> " + " >> _) - unusedSet.toList.sorted.map(app :> " - " >> _) - app :> "=" * 80, - ) - throw TypeCheckFail(if (silent) None else Some(app.toString)) - sem - }, - ) - def apply( - target: Option[String], - ignore: Ignore, - log: Boolean, - silent: Boolean, - ): Semantics = - val targets = getInitTargets(target, silent) - if (!silent) println(s"- ${targets.size} functions are initial targets.") - apply(targets, ignore, log, silent) + /** post-: t-Domainprocessing */ + def postProcess: Unit = + if (log) logging + var unusedSet = ignore.names + val detected = mismatches.filter(mismatch => { + val name = mismatch.func.name + unusedSet -= name + !ignore.names.contains(name) + }) + if (!detected.isEmpty || !unusedSet.isEmpty) + val app = new Appender + // show detected type mismatches + if (!detected.isEmpty) + app :> "* " >> detected.size + app >> " type mismatches are detected." + // show unused names + if (!unusedSet.isEmpty) + app :> "* " >> unusedSet.size + app >> " names are not used to ignore mismatches." + detected.toList.map(_.toString).sorted.map(app :> _) + // show help message about how to use the ignorance system + ignore.filename.map(path => + if (ignore.update) + dumpJson( + name = "algorithm names for the ignorance system", + data = mismatches.map(_.func.name).toList.sorted, + filename = path, + noSpace = false, + ) + else + app :> "=" * 80 + app :> "To suppress this error message, " + app >> "add/remove the following names to `" >> path >> "`:" + detected.map(_.func.name).toList.sorted.map(app :> " + " >> _) + unusedSet.toList.sorted.map(app :> " - " >> _) + app :> "=" * 80, + ) + throw TypeCheckFail(if (silent) None else Some(app.toString)) - // all entry node points - def getNps(targets: List[Func]): List[NodePoint[Node]] = for { + // --------------------------------------------------------------------------- + // private helpers + // --------------------------------------------------------------------------- + + /** all entry node points */ + private def getNps(targets: List[Func]): List[NodePoint[Node]] = for { func <- targets entry = func.entry view = getView(func) } yield NodePoint(func, entry, view) - // get initial abstract states in each node point - def initNpMap(targets: List[Func]): Map[NodePoint[Node], AbsState] = (for { - np @ NodePoint(func, _, _) <- getNps(targets) - st = getState(func) - } yield np -> st).toMap - - // get view from a function - def getView(func: Func): View = View() + /** get initial abstract states in each node point */ + private def getInitNpMap( + targets: List[Func], + ): Map[NodePoint[Node], AbsState] = + (for { + np @ NodePoint(func, _, _) <- getNps(targets) + st = getState(func) + } yield np -> st).toMap - // get initial state of function - def getState(func: Func): AbsState = func.params.foldLeft(AbsState.Empty) { - case (st, Param(x, ty, opt, _)) => - var v = AbsValue(ty.ty) - if (opt) v ⊔= AbsValue.absentTop - st.update(x, v) - } + /** get view from a function */ + private def getView(func: Func): View = View() - /** find initial analysis targets based on a given regex pattern */ - def getInitTargets( - target: Option[String], - silent: Boolean = false, - ): List[Func] = - // find all possible initial analysis target functions - val allFuncs = cfg.funcs.filter(_.isParamTysDefined) - target.fold(allFuncs)(pattern => { - val funcs = allFuncs.filter(f => pattern.r.matches(f.name)) - if (!silent && funcs.isEmpty) - warn(s"failed to find functions matched with the pattern `$pattern`.") - funcs - }) + /** get initial state of function */ + private def getState(func: Func): AbsState = + func.params.foldLeft(AbsState.Empty) { + case (st, Param(x, ty, opt, _)) => + var v = AbsValue(ty.ty) + if (opt) v ⊔= AbsValue.absentTop + st.update(x, v) + } - // logging mode - def logging(sem: Semantics): Unit = { + /** logging mode */ + private def logging: Unit = { mkdir(ANALYZE_LOG_DIR) dumpFile( name = "type analysis result", @@ -284,19 +294,7 @@ class TypeAnalyzer( ) } } -object TypeAnalyzer { - // set type domains - initDomain( - stateDomain = state.TypeDomain, - valueDomain = value.TypeDomain, - retDomain = ret.TypeDomain, - ) - - // no sensitivity - IR_SENS = false - - // use type refinement - USE_REFINE = true +object TypeAnalyzer: /** algorithm names used in ignoring type mismatches */ case class Ignore( @@ -319,4 +317,3 @@ object TypeAnalyzer { returnType: Boolean = true, uncheckedAbrupt: Boolean = false, ) -} diff --git a/src/main/scala/esmeta/analyzer/TypeMismatch.scala b/src/main/scala/esmeta/analyzer/TypeMismatch.scala index 22cb57c7b4..183a5def57 100644 --- a/src/main/scala/esmeta/analyzer/TypeMismatch.scala +++ b/src/main/scala/esmeta/analyzer/TypeMismatch.scala @@ -5,33 +5,36 @@ import esmeta.ir.{Func => _, *} import esmeta.ty.* import esmeta.util.* -/** specification type mismatches */ -sealed abstract class TypeMismatch( - val ap: AnalysisPoint, -) extends AnalyzerElem { - inline def func: Func = ap.func -} +trait TypeMismatchDecl { self: Analyzer => + + /** specification type mismatches */ + sealed abstract class TypeMismatch( + val ap: AnalysisPoint, + ) extends AnalyzerElem { + inline def func: Func = ap.func + } -/** parameter type mismatches */ -case class ParamTypeMismatch( - aap: ArgAssignPoint[Node], - actual: ValueTy, -) extends TypeMismatch(aap) + /** parameter type mismatches */ + case class ParamTypeMismatch( + aap: ArgAssignPoint[Node], + actual: ValueTy, + ) extends TypeMismatch(aap) -/** return type mismatches */ -case class ReturnTypeMismatch( - irp: InternalReturnPoint, - actual: ValueTy, -) extends TypeMismatch(irp) + /** return type mismatches */ + case class ReturnTypeMismatch( + irp: InternalReturnPoint, + actual: ValueTy, + ) extends TypeMismatch(irp) -/** arity mismatches */ -case class ArityMismatch( - cp: CallPoint[Node], - actual: Int, -) extends TypeMismatch(cp) + /** arity mismatches */ + case class ArityMismatch( + cp: CallPoint[Node], + actual: Int, + ) extends TypeMismatch(cp) -/** unchecked abrupt completion mismatches */ -case class UncheckedAbruptCompletionMismatch( - riap: ReturnIfAbruptPoint, - actual: ValueTy, -) extends TypeMismatch(riap) + /** unchecked abrupt completion mismatches */ + case class UncheckedAbruptCompletionMismatch( + riap: ReturnIfAbruptPoint, + actual: ValueTy, + ) extends TypeMismatch(riap) +} diff --git a/src/main/scala/esmeta/analyzer/View.scala b/src/main/scala/esmeta/analyzer/View.scala index 4be76f275d..dadf5ef52b 100644 --- a/src/main/scala/esmeta/analyzer/View.scala +++ b/src/main/scala/esmeta/analyzer/View.scala @@ -2,17 +2,20 @@ package esmeta.analyzer import esmeta.cfg.* -/** view abstraction for analysis sensitivities */ -case class View( - calls: List[Call] = Nil, - loops: List[LoopCtxt] = Nil, - intraLoopDepth: Int = 0, - // TODO tys: List[Type] = Nil, -) extends AnalyzerElem { +trait ViewDecl { self: Analyzer => - /** empty check */ - def isEmpty: Boolean = this == View() -} + /** view abstraction for analysis sensitivities */ + case class View( + calls: List[Call] = Nil, + loops: List[LoopCtxt] = Nil, + intraLoopDepth: Int = 0, + // TODO tys: List[Type] = Nil, + ) extends AnalyzerElem { + + /** empty check */ + def isEmpty: Boolean = this == View() + } -/** loop context */ -case class LoopCtxt(loop: Branch, depth: Int) + /** loop context */ + case class LoopCtxt(loop: Branch, depth: Int) +} diff --git a/src/main/scala/esmeta/analyzer/domain/AValue.scala b/src/main/scala/esmeta/analyzer/domain/AValue.scala index bb1c28e25f..d9222506fe 100644 --- a/src/main/scala/esmeta/analyzer/domain/AValue.scala +++ b/src/main/scala/esmeta/analyzer/domain/AValue.scala @@ -7,73 +7,76 @@ import esmeta.ir.Name import esmeta.es.Ast import esmeta.util.BaseUtils.* -/** values for analysis */ -type AValue = AComp | APureValue -object AValue: - /** from original values */ - def from(value: Value): AValue = value match - case comp: Comp => AComp.from(comp) - case pureValue: PureValue => APureValue.from(pureValue) +trait AValueDecl { self: Self => -/** completion values for analysis */ -case class AComp(ty: Const, value: APureValue, target: Option[String]) -object AComp: - /** from original completions */ - def from(comp: Comp): AComp = - val Comp(ty, value, target) = comp - AComp(ty, APureValue.from(value), target) + /** values for analysis */ + type AValue = AComp | APureValue + object AValue: + /** from original values */ + def from(value: Value): AValue = value match + case comp: Comp => AComp.from(comp) + case pureValue: PureValue => APureValue.from(pureValue) -/** pure values for analysis */ -type APureValue = Part | AClo | ACont | AstValue | Nt | Math | Const | - CodeUnit | SimpleValue -object APureValue: - /** from original pure values */ - def from(pureValue: PureValue): APureValue = pureValue match - case addr: Addr => Part.from(addr) - case clo: Clo => AClo.from(clo) - case cont: Cont => ACont.from(cont) - case astValue: AstValue => astValue - case nt: Nt => nt - case math: Math => math - case const: Const => const - case codeUnit: CodeUnit => codeUnit - case simpleValue: SimpleValue => simpleValue + /** completion values for analysis */ + case class AComp(ty: Const, value: APureValue, target: Option[String]) + object AComp: + /** from original completions */ + def from(comp: Comp): AComp = + val Comp(ty, value, target) = comp + AComp(ty, APureValue.from(value), target) -/** address partitions */ -sealed trait Part: - /** check named elements */ - def isNamed: Boolean = this match - case Named(_) | SubMap(Named(_)) => true - case _ => false + /** pure values for analysis */ + type APureValue = Part | AClo | ACont | AstValue | Nt | Math | Const | + CodeUnit | SimpleValue + object APureValue: + /** from original pure values */ + def from(pureValue: PureValue): APureValue = pureValue match + case addr: Addr => Part.from(addr) + case clo: Clo => AClo.from(clo) + case cont: Cont => ACont.from(cont) + case astValue: AstValue => astValue + case nt: Nt => nt + case math: Math => math + case const: Const => const + case codeUnit: CodeUnit => codeUnit + case simpleValue: SimpleValue => simpleValue - /** get base elements */ - def base: Base -object Part: - /** from original addresses */ - def from(addr: Addr): Part = addr match - case NamedAddr(name) => - name match - case subMapPattern(base) => SubMap(Named(base)) - case name => Named(name) - case _ => error(s"impossible to convert to Loc: $addr") - private val subMapPattern = "(.+).SubMap".r -sealed trait Base extends Part { def base = this } -case class Named(name: String) extends Base -case class AllocSite(k: Int, view: View) extends Base -case class SubMap(base: Base) extends Part + /** address partitions */ + sealed trait Part: + /** check named elements */ + def isNamed: Boolean = this match + case Named(_) | SubMap(Named(_)) => true + case _ => false -/** closures */ -case class AClo(func: Func, captured: Map[Name, AbsValue]) -object AClo: - /** from original closures */ - def from(clo: Clo): AClo = - val Clo(func, captured) = clo - val newCaptured = captured.map((x, v) => x -> AbsValue(v)).toMap - AClo(func, newCaptured) + /** get base elements */ + def base: Base + object Part: + /** from original addresses */ + def from(addr: Addr): Part = addr match + case NamedAddr(name) => + name match + case subMapPattern(base) => SubMap(Named(base)) + case name => Named(name) + case _ => error(s"impossible to convert to Loc: $addr") + private val subMapPattern = "(.+).SubMap".r + sealed trait Base extends Part { def base = this } + case class Named(name: String) extends Base + case class AllocSite(k: Int, view: View) extends Base + case class SubMap(base: Base) extends Part -/** continuations */ -case class ACont(target: NodePoint[Node], captured: Map[Name, AbsValue]) -object ACont: - /** from original continuations */ - def from(cont: Cont): ACont = - error(s"impossible to convert continuations: $cont") + /** closures */ + case class AClo(func: Func, captured: Map[Name, AbsValue]) + object AClo: + /** from original closures */ + def from(clo: Clo): AClo = + val Clo(func, captured) = clo + val newCaptured = captured.map((x, v) => x -> AbsValue(v)).toMap + AClo(func, newCaptured) + + /** continuations */ + case class ACont(target: NodePoint[Node], captured: Map[Name, AbsValue]) + object ACont: + /** from original continuations */ + def from(cont: Cont): ACont = + error(s"impossible to convert continuations: $cont") +} diff --git a/src/main/scala/esmeta/analyzer/domain/AbsRefValue.scala b/src/main/scala/esmeta/analyzer/domain/AbsRefValue.scala index 5e007821bb..caf21ae2a4 100644 --- a/src/main/scala/esmeta/analyzer/domain/AbsRefValue.scala +++ b/src/main/scala/esmeta/analyzer/domain/AbsRefValue.scala @@ -3,10 +3,13 @@ package esmeta.analyzer.domain import esmeta.analyzer.* import esmeta.ir.* -// basic abstract reference values -sealed trait AbsRefValue: - override def toString: String = this match - case AbsRefId(id) => s"$id" - case AbsRefProp(base, prop) => s"$base[$prop]" -case class AbsRefId(id: Id) extends AbsRefValue -case class AbsRefProp(base: AbsValue, prop: AbsValue) extends AbsRefValue +trait AbsRefValueDecl { self: Self => + + /** basic abstract reference values */ + sealed trait AbsRefValue: + override def toString: String = this match + case AbsRefId(id) => s"$id" + case AbsRefProp(base, prop) => s"$base[$prop]" + case class AbsRefId(id: Id) extends AbsRefValue + case class AbsRefProp(base: AbsValue, prop: AbsValue) extends AbsRefValue +} diff --git a/src/main/scala/esmeta/analyzer/domain/Domain.scala b/src/main/scala/esmeta/analyzer/domain/Domain.scala index 6ae3d18fa3..7a3e51dde7 100644 --- a/src/main/scala/esmeta/analyzer/domain/Domain.scala +++ b/src/main/scala/esmeta/analyzer/domain/Domain.scala @@ -5,76 +5,79 @@ import esmeta.util.* import esmeta.util.Appender.* import esmeta.util.BaseUtils.* -/** domain */ -trait Domain[A] { self => +trait DomainDecl { self: Self => - /** top element */ - def Top: Elem + /** domain */ + trait Domain[A] { self => - /** bottom element */ - def Bot: Elem + /** top element */ + def Top: Elem - /** element */ - type Elem <: Appendable + /** bottom element */ + def Bot: Elem - /** conversion to iterable object */ - given Conversion[Elem, Iterable[A]] = _.toIterable(true) + /** element */ + type Elem <: Appendable - /** abstraction functions */ - def alpha(elems: Iterable[A]): Elem - def alpha(elems: A*): Elem = alpha(elems) - def apply(elems: Iterable[A]): Elem = alpha(elems) - def apply(elems: A*): Elem = alpha(elems) + /** conversion to iterable object */ + given Conversion[Elem, Iterable[A]] = _.toIterable(true) - /** appender */ - given rule: Rule[Elem] + /** abstraction functions */ + def alpha(elems: Iterable[A]): Elem + def alpha(elems: A*): Elem = alpha(elems) + def apply(elems: Iterable[A]): Elem = alpha(elems) + def apply(elems: A*): Elem = alpha(elems) - /** appendable */ - trait Appendable { this: Elem => + /** appender */ + given rule: Rule[Elem] - /** conversion to string */ - override def toString: String = stringify(this) - } + /** appendable */ + trait Appendable { this: Elem => + + /** conversion to string */ + override def toString: String = stringify(this) + } - /** optional domain */ - lazy val optional: OptionDomain[A, this.type] = OptionDomain(this) + /** optional domain */ + lazy val optional: OptionDomain[A, this.type] = OptionDomain(this) - /** domain element interfaces */ - extension (elem: Elem) { + /** domain element interfaces */ + extension (elem: Elem) { - /** partial order */ - def ⊑(that: Elem): Boolean + /** partial order */ + def ⊑(that: Elem): Boolean - /** not partial order */ - def !⊑(that: Elem): Boolean = !(elem ⊑ that) + /** not partial order */ + def !⊑(that: Elem): Boolean = !(elem ⊑ that) - /** join operator */ - def ⊔(that: Elem): Elem + /** join operator */ + def ⊔(that: Elem): Elem - /** meet operator */ - def ⊓(that: Elem): Elem = Top + /** meet operator */ + def ⊓(that: Elem): Elem = Top - /** prune operator */ - def --(that: Elem): Elem = elem + /** prune operator */ + def --(that: Elem): Elem = elem - /** top check */ - def isTop: Boolean = elem == Top + /** top check */ + def isTop: Boolean = elem == Top - /** bottom check */ - def isBottom: Boolean = elem == Bot + /** bottom check */ + def isBottom: Boolean = elem == Bot - /** concretization function */ - def gamma: BSet[A] = if (isBottom) Fin() else Inf + /** concretization function */ + def gamma: BSet[A] = if (isBottom) Fin() else Inf - /** get single value */ - def getSingle: Flat[A] = if (isBottom) Zero else Many + /** get single value */ + def getSingle: Flat[A] = if (isBottom) Zero else Many - /** conversion to iterable */ - def toIterable(stop: Boolean): Iterable[A] = new Iterable[A]: - final def iterator: Iterator[A] = elem.gamma match - case Inf => - if (stop) exploded(s"impossible to iterate infinite values") - else Nil.iterator - case Fin(set) => set.iterator + /** conversion to iterable */ + def toIterable(stop: Boolean): Iterable[A] = new Iterable[A]: + final def iterator: Iterator[A] = elem.gamma match + case Inf => + if (stop) exploded(s"impossible to iterate infinite values") + else Nil.iterator + case Fin(set) => set.iterator + } } } diff --git a/src/main/scala/esmeta/analyzer/domain/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/FlatDomain.scala index daafcdf7cd..56e1f0edb2 100644 --- a/src/main/scala/esmeta/analyzer/domain/FlatDomain.scala +++ b/src/main/scala/esmeta/analyzer/domain/FlatDomain.scala @@ -4,95 +4,98 @@ import esmeta.analyzer.* import esmeta.util.* import esmeta.util.Appender.* -/** flat domain */ -trait FlatDomain[A]( - val topName: String, // name of top element - val totalOpt: BSet[A] = Inf, // total elements -) extends Domain[A] { - - /** elements */ - sealed trait Elem extends Iterable[A] with Appendable { - - /** iterators */ - final def iterator: Iterator[A] = (this match { - case Bot => Nil - case Base(elem) => List(elem) - case Top => - totalOpt match - case Fin(set) => set - case Inf => - exploded(s"impossible to concretize the top value of $topName.") - }).iterator - } - - /** top element */ - object Top extends Elem - - /** single elements */ - case class Base(elem: A) extends Elem - - /** bottom element */ - object Bot extends Elem - - /** abstraction functions */ - def alpha(elems: Iterable[A]): Elem = - val set = elems.toSet - set.size match - case 0 => Bot - case 1 => Base(set.head) - case _ => Top - - /** appender */ - given rule: Rule[Elem] = (app, elem) => - elem match - case Bot => app >> "⊥" - case Top => app >> topName - case Base(v) => app >> v.toString - - /** element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = (elem, that) match - case (Bot, _) | (_, Top) => true - case (_, Bot) | (Top, _) => false - case (Base(l), Base(r)) => l == r - - /** join operator */ - def ⊔(that: Elem): Elem = (elem, that) match - case (Bot, _) | (_, Top) => that - case (_, Bot) | (Top, _) => elem - case (Base(l), Base(r)) => if (l == r) elem else Top - - /** meet operator */ - override def ⊓(that: Elem): Elem = (elem, that) match - case (Bot, _) | (_, Top) => elem - case (_, Bot) | (Top, _) => that - case (Base(l), Base(r)) => if (l == r) elem else Bot - - /** prune operator */ - override def --(that: Elem): Elem = (elem, that) match - case (Bot, _) | (_, Top) => Bot - case (_, Bot) => elem - case (Top, _: Base) => Top - case (Base(l), Base(r)) => if (l == r) Bot else elem - - /** contains check */ - def contains(target: A): Boolean = target match - case Bot => false - case Top => true - case Base(x) => x == target - - /** concretization function */ - override def gamma: BSet[A] = elem match - case Top => totalOpt - case Base(elem) => Fin(Set(elem)) - case Bot => Fin(Set()) - - /** get single value */ - override def getSingle: Flat[A] = elem match - case Top => Many - case Base(elem) => One(elem) - case Bot => Zero +trait FlatDomainDecl { self: Self => + + /** flat domain */ + trait FlatDomain[A]( + val topName: String, // name of top element + val totalOpt: BSet[A] = Inf, // total elements + ) extends Domain[A] { + + /** elements */ + sealed trait Elem extends Iterable[A] with Appendable { + + /** iterators */ + final def iterator: Iterator[A] = (this match { + case Bot => Nil + case Base(elem) => List(elem) + case Top => + totalOpt match + case Fin(set) => set + case Inf => + exploded(s"impossible to concretize the top value of $topName.") + }).iterator + } + + /** top element */ + object Top extends Elem + + /** single elements */ + case class Base(elem: A) extends Elem + + /** bottom element */ + object Bot extends Elem + + /** abstraction functions */ + def alpha(elems: Iterable[A]): Elem = + val set = elems.toSet + set.size match + case 0 => Bot + case 1 => Base(set.head) + case _ => Top + + /** appender */ + given rule: Rule[Elem] = (app, elem) => + elem match + case Bot => app >> "⊥" + case Top => app >> topName + case Base(v) => app >> v.toString + + /** element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = (elem, that) match + case (Bot, _) | (_, Top) => true + case (_, Bot) | (Top, _) => false + case (Base(l), Base(r)) => l == r + + /** join operator */ + def ⊔(that: Elem): Elem = (elem, that) match + case (Bot, _) | (_, Top) => that + case (_, Bot) | (Top, _) => elem + case (Base(l), Base(r)) => if (l == r) elem else Top + + /** meet operator */ + override def ⊓(that: Elem): Elem = (elem, that) match + case (Bot, _) | (_, Top) => elem + case (_, Bot) | (Top, _) => that + case (Base(l), Base(r)) => if (l == r) elem else Bot + + /** prune operator */ + override def --(that: Elem): Elem = (elem, that) match + case (Bot, _) | (_, Top) => Bot + case (_, Bot) => elem + case (Top, _: Base) => Top + case (Base(l), Base(r)) => if (l == r) Bot else elem + + /** contains check */ + def contains(target: A): Boolean = target match + case Bot => false + case Top => true + case Base(x) => x == target + + /** concretization function */ + override def gamma: BSet[A] = elem match + case Top => totalOpt + case Base(elem) => Fin(Set(elem)) + case Bot => Fin(Set()) + + /** get single value */ + override def getSingle: Flat[A] = elem match + case Top => Many + case Base(elem) => One(elem) + case Bot => Zero + } } } diff --git a/src/main/scala/esmeta/analyzer/domain/OptionDomain.scala b/src/main/scala/esmeta/analyzer/domain/OptionDomain.scala index 896e907eea..1b75c924a3 100644 --- a/src/main/scala/esmeta/analyzer/domain/OptionDomain.scala +++ b/src/main/scala/esmeta/analyzer/domain/OptionDomain.scala @@ -4,75 +4,78 @@ import esmeta.analyzer.* import esmeta.util.* import esmeta.util.Appender.* -/** option domain */ -class OptionDomain[V, D <: Domain[V] with Singleton](val AbsV: D) - extends Domain[Option[V]] { +trait OptionDomainDecl { self: Self => - /** astract V type domain */ - type AbsV = AbsV.Elem + /** option domain */ + class OptionDomain[V, D <: Domain[V] with Singleton](val AbsV: D) + extends Domain[Option[V]] { - /** elements */ - case class Elem(value: AbsV, absent: AbsAbsent) extends Appendable + /** astract V type domain */ + type AbsV = AbsV.Elem - /** top element */ - val Top: Elem = Elem(AbsV.Top, AbsAbsent.Top) + /** elements */ + case class Elem(value: AbsV, absent: AbsAbsent) extends Appendable - /** bottom element */ - val Bot: Elem = Elem(AbsV.Bot, AbsAbsent.Bot) + /** top element */ + val Top: Elem = Elem(AbsV.Top, AbsAbsent.Top) - /** empty element */ - val Empty: Elem = Elem(AbsV.Bot, AbsAbsent.Top) + /** bottom element */ + val Bot: Elem = Elem(AbsV.Bot, AbsAbsent.Bot) - /** abstraction functions */ - def alpha(xs: Iterable[Option[V]]): Elem = Elem( - AbsV(xs.collect { case Some(x) => x }), - if (xs.exists(_ == None)) AbsAbsent.Top else AbsAbsent.Bot, - ) + /** empty element */ + val Empty: Elem = Elem(AbsV.Bot, AbsAbsent.Top) - /** appender */ - given rule: Rule[Elem] = (app, elem) => - app >> elem.value >> (if (elem.absent.isTop) "?" else "") - - /** element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = - elem.value ⊑ that.value && - elem.absent ⊑ that.absent - - /** join operator */ - def ⊔(that: Elem): Elem = Elem( - elem.value ⊔ that.value, - elem.absent ⊔ that.absent, - ) - - /** meet operator */ - override def ⊓(that: Elem): Elem = Elem( - elem.value ⊓ that.value, - elem.absent ⊓ that.absent, - ) - - /** prune operator */ - override def --(that: Elem): Elem = Elem( - elem.value -- that.value, - elem.absent -- that.absent, + /** abstraction functions */ + def alpha(xs: Iterable[Option[V]]): Elem = Elem( + AbsV(xs.collect { case Some(x) => x }), + if (xs.exists(_ == None)) AbsAbsent.Top else AbsAbsent.Bot, ) - /** concretization function */ - override def gamma: BSet[Option[V]] = - elem.value.gamma.map(Some(_)) ⊔ - (if (elem.absent.isTop) Fin(None) else Fin()) - - /** get single value */ - override def getSingle: Flat[Option[V]] = - elem.value.getSingle.map(Some(_)) ⊔ - (if (elem.absent.isTop) One(None) else Zero) - - /** fold operator */ - def fold( - domain: Domain[_] with Singleton, - )(default: domain.Elem)(f: AbsV => domain.Elem): domain.Elem = - f(elem.value) ⊔ (if (elem.absent.isTop) default else domain.Bot) + /** appender */ + given rule: Rule[Elem] = (app, elem) => + app >> elem.value >> (if (elem.absent.isTop) "?" else "") + + /** element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = + elem.value ⊑ that.value && + elem.absent ⊑ that.absent + + /** join operator */ + def ⊔(that: Elem): Elem = Elem( + elem.value ⊔ that.value, + elem.absent ⊔ that.absent, + ) + + /** meet operator */ + override def ⊓(that: Elem): Elem = Elem( + elem.value ⊓ that.value, + elem.absent ⊓ that.absent, + ) + + /** prune operator */ + override def --(that: Elem): Elem = Elem( + elem.value -- that.value, + elem.absent -- that.absent, + ) + + /** concretization function */ + override def gamma: BSet[Option[V]] = + elem.value.gamma.map(Some(_)) ⊔ + (if (elem.absent.isTop) Fin(None) else Fin()) + + /** get single value */ + override def getSingle: Flat[Option[V]] = + elem.value.getSingle.map(Some(_)) ⊔ + (if (elem.absent.isTop) One(None) else Zero) + + /** fold operator */ + def fold( + domain: Domain[_] with Singleton, + )(default: domain.Elem)(f: AbsV => domain.Elem): domain.Elem = + f(elem.value) ⊔ (if (elem.absent.isTop) default else domain.Bot) + } } } diff --git a/src/main/scala/esmeta/analyzer/domain/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/SetDomain.scala index c4006271f2..e1597018d2 100644 --- a/src/main/scala/esmeta/analyzer/domain/SetDomain.scala +++ b/src/main/scala/esmeta/analyzer/domain/SetDomain.scala @@ -4,98 +4,101 @@ import esmeta.util.Appender.* import esmeta.util.* import esmeta.analyzer.exploded -/** set domain */ -trait SetDomain[A]( - val topName: String, // name of top element - val maxSizeOpt: Option[Int] = None, // max size of set - val totalOpt: BSet[A] = Inf, // total elements -) extends Domain[A] { - - /** elements */ - sealed trait Elem extends Iterable[A] with Appendable { - - /** iterators */ - final def iterator: Iterator[A] = (this match { - case Base(set) => set - case Top => - totalOpt match - case Fin(set) => set - case Inf => - exploded(s"impossible to concretize the top value of $topName.") - }).iterator - } - - /** top element */ - object Top extends Elem - - /** finite elements */ - case class Base(set: Set[A]) extends Elem - - /** bottom element */ - val Bot = Base(Set()) - - /** abstraction functions */ - def alpha(elems: Iterable[A]): Elem = - val set = elems.toSet - maxSizeOpt match - case Some(max) if set.size > max => Top - case _ => Base(set) - - /** appender */ - given rule: Rule[Elem] = (app, elem) => - elem match - case Top => app >> topName - case Base(set) => - app >> (set.size match { - case 0 => "⊥" - case 1 => set.head.toString - case _ => set.toList.map(_.toString).sorted.mkString("{", ", ", "}") - }) - - /** element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = (elem, that) match - case (_, Top) => true - case (Top, _) => false - case (Base(lset), Base(rset)) => lset subsetOf rset - - /** join operator */ - def ⊔(that: Elem): Elem = (elem, that) match - case (Top, _) | (_, Top) => Top - case (Base(lset), Base(rset)) => alpha(lset ++ rset) - - /** meet operator */ - override def ⊓(that: Elem): Elem = (elem, that) match - case (Top, _) => that - case (_, Top) => elem - case (Base(lset), Base(rset)) => Base(lset intersect rset) - - /** prune operator */ - override def --(that: Elem): Elem = (elem, that) match - case (Bot, _) | (_, Top) => Bot - case (_, Bot) => elem - case (Top, _: Base) => Top - case (Base(l), Base(r)) => if (l subsetOf r) Bot else Base(l -- r) - - /** concretization function */ - override def gamma: BSet[A] = elem match - case Top => totalOpt - case Base(set) => Fin(set) - - /** get single value */ - override def getSingle: Flat[A] = elem match - case Base(set) => - set.size match - case 0 => Zero - case 1 => One(set.head) - case _ => Many - case _ => Many - - /** contains check */ - def contains(target: A): Boolean = elem match - case Top => true - case Base(set) => set contains target +trait SetDomainDecl { self: Self => + + /** set domain */ + trait SetDomain[A]( + val topName: String, // name of top element + val maxSizeOpt: Option[Int] = None, // max size of set + val totalOpt: BSet[A] = Inf, // total elements + ) extends Domain[A] { + + /** elements */ + sealed trait Elem extends Iterable[A] with Appendable { + + /** iterators */ + final def iterator: Iterator[A] = (this match { + case Base(set) => set + case Top => + totalOpt match + case Fin(set) => set + case Inf => + exploded(s"impossible to concretize the top value of $topName.") + }).iterator + } + + /** top element */ + object Top extends Elem + + /** finite elements */ + case class Base(set: Set[A]) extends Elem + + /** bottom element */ + val Bot = Base(Set()) + + /** abstraction functions */ + def alpha(elems: Iterable[A]): Elem = + val set = elems.toSet + maxSizeOpt match + case Some(max) if set.size > max => Top + case _ => Base(set) + + /** appender */ + given rule: Rule[Elem] = (app, elem) => + elem match + case Top => app >> topName + case Base(set) => + app >> (set.size match { + case 0 => "⊥" + case 1 => set.head.toString + case _ => set.toList.map(_.toString).sorted.mkString("{", ", ", "}") + }) + + /** element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = (elem, that) match + case (_, Top) => true + case (Top, _) => false + case (Base(lset), Base(rset)) => lset subsetOf rset + + /** join operator */ + def ⊔(that: Elem): Elem = (elem, that) match + case (Top, _) | (_, Top) => Top + case (Base(lset), Base(rset)) => alpha(lset ++ rset) + + /** meet operator */ + override def ⊓(that: Elem): Elem = (elem, that) match + case (Top, _) => that + case (_, Top) => elem + case (Base(lset), Base(rset)) => Base(lset intersect rset) + + /** prune operator */ + override def --(that: Elem): Elem = (elem, that) match + case (Bot, _) | (_, Top) => Bot + case (_, Bot) => elem + case (Top, _: Base) => Top + case (Base(l), Base(r)) => if (l subsetOf r) Bot else Base(l -- r) + + /** concretization function */ + override def gamma: BSet[A] = elem match + case Top => totalOpt + case Base(set) => Fin(set) + + /** get single value */ + override def getSingle: Flat[A] = elem match + case Base(set) => + set.size match + case 0 => Zero + case 1 => One(set.head) + case _ => Many + case _ => Many + + /** contains check */ + def contains(target: A): Boolean = elem match + case Top => true + case Base(set) => set contains target + } } } diff --git a/src/main/scala/esmeta/analyzer/domain/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/SimpleDomain.scala index 49342d70eb..41cc56e21b 100644 --- a/src/main/scala/esmeta/analyzer/domain/SimpleDomain.scala +++ b/src/main/scala/esmeta/analyzer/domain/SimpleDomain.scala @@ -4,75 +4,78 @@ import esmeta.analyzer.* import esmeta.util.* import esmeta.util.Appender.* -/** simple domain */ -trait SimpleDomain[A]( - val topName: String, // name of top element - val totalOpt: BSet[A] = Inf, // total elements -) extends Domain[A] { - - /** elements */ - sealed trait Elem extends Iterable[A] with Appendable { - - /** iterators */ - final def iterator: Iterator[A] = (this match { - case Bot => Nil - case Top => - totalOpt match - case Fin(set) => set - case Inf => - exploded(s"impossible to concretize the top value of $topName.") - }).iterator - } +trait SimpleDomainDecl { self: Self => + + /** simple domain */ + trait SimpleDomain[A]( + val topName: String, // name of top element + val totalOpt: BSet[A] = Inf, // total elements + ) extends Domain[A] { + + /** elements */ + sealed trait Elem extends Iterable[A] with Appendable { + + /** iterators */ + final def iterator: Iterator[A] = (this match { + case Bot => Nil + case Top => + totalOpt match + case Fin(set) => set + case Inf => + exploded(s"impossible to concretize the top value of $topName.") + }).iterator + } + + /** top element */ + object Top extends Elem + + /** bottom element */ + object Bot extends Elem + + /** abstraction functions */ + def alpha(elems: Iterable[A]): Elem = if (elems.isEmpty) Bot else Top + + /** appender */ + given rule: Rule[Elem] = (app, elem) => + elem match + case Bot => app >> "⊥" + case Top => app >> topName.toString + + /** element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = (elem, that) match + case (Bot, _) | (_, Top) => true + case (_, Bot) | (Top, _) => false + + /** join operator */ + def ⊔(that: Elem): Elem = (elem, that) match + case (Bot, _) | (_, Top) => that + case (_, Bot) | (Top, _) => elem + + /** meet operator */ + override def ⊓(that: Elem): Elem = (elem, that) match + case (Bot, _) | (_, Top) => elem + case (_, Bot) | (Top, _) => that + + /** prune operator */ + override def --(that: Elem): Elem = that match + case Bot => elem + case Top => Bot + + /** concretization function */ + override def gamma: BSet[A] = elem match + case Bot => Fin(Set()) + case Top => totalOpt - /** top element */ - object Top extends Elem - - /** bottom element */ - object Bot extends Elem - - /** abstraction functions */ - def alpha(elems: Iterable[A]): Elem = if (elems.isEmpty) Bot else Top - - /** appender */ - given rule: Rule[Elem] = (app, elem) => - elem match - case Bot => app >> "⊥" - case Top => app >> topName.toString - - /** element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = (elem, that) match - case (Bot, _) | (_, Top) => true - case (_, Bot) | (Top, _) => false - - /** join operator */ - def ⊔(that: Elem): Elem = (elem, that) match - case (Bot, _) | (_, Top) => that - case (_, Bot) | (Top, _) => elem - - /** meet operator */ - override def ⊓(that: Elem): Elem = (elem, that) match - case (Bot, _) | (_, Top) => elem - case (_, Bot) | (Top, _) => that - - /** prune operator */ - override def --(that: Elem): Elem = that match - case Bot => elem - case Top => Bot - - /** concretization function */ - override def gamma: BSet[A] = elem match - case Bot => Fin(Set()) - case Top => totalOpt - - /** get single value */ - override def getSingle: Flat[A] = elem match - case Bot => Zero - case Top => - totalOpt match - case Fin(set) if set.size == 1 => One(set.head) - case _ => Many + /** get single value */ + override def getSingle: Flat[A] = elem match + case Bot => Zero + case Top => + totalOpt match + case Fin(set) if set.size == 1 => One(set.head) + case _ => Many + } } } diff --git a/src/main/scala/esmeta/analyzer/domain/absent/Domain.scala b/src/main/scala/esmeta/analyzer/domain/absent/AbsentDomain.scala similarity index 50% rename from src/main/scala/esmeta/analyzer/domain/absent/Domain.scala rename to src/main/scala/esmeta/analyzer/domain/absent/AbsentDomain.scala index 7ff34710f5..91b8b1c0f7 100644 --- a/src/main/scala/esmeta/analyzer/domain/absent/Domain.scala +++ b/src/main/scala/esmeta/analyzer/domain/absent/AbsentDomain.scala @@ -4,5 +4,8 @@ import esmeta.analyzer.* import esmeta.analyzer.domain.* import esmeta.state.* -/** abstract absent domain */ -trait Domain extends domain.Domain[Absent] +trait AbsentDomainDecl { self: Self => + + /** abstract absent domain */ + trait AbsentDomain extends Domain[Absent] +} diff --git a/src/main/scala/esmeta/analyzer/domain/absent/AbsentSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/absent/AbsentSimpleDomain.scala new file mode 100644 index 0000000000..7b769dddad --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/absent/AbsentSimpleDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.absent + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.* + +trait AbsentSimpleDomainDecl { self: Self => + + /** simple domain for absent values */ + object AbsentSimpleDomain + extends AbsentDomain + with SimpleDomain("absent", Fin(Absent)) +} diff --git a/src/main/scala/esmeta/analyzer/domain/absent/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/absent/SimpleDomain.scala deleted file mode 100644 index caf421f34e..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/absent/SimpleDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.absent - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.* - -/** simple domain for absent values */ -object SimpleDomain - extends absent.Domain - with SimpleDomain("absent", Fin(Absent)) diff --git a/src/main/scala/esmeta/analyzer/domain/absent/package.scala b/src/main/scala/esmeta/analyzer/domain/absent/package.scala new file mode 100644 index 0000000000..eaf6432822 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/absent/package.scala @@ -0,0 +1,8 @@ +package esmeta.analyzer.domain.absent + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl extends AbsentDomainDecl with AbsentSimpleDomainDecl { self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/astValue/Domain.scala b/src/main/scala/esmeta/analyzer/domain/astValue/AstValueDomain.scala similarity index 51% rename from src/main/scala/esmeta/analyzer/domain/astValue/Domain.scala rename to src/main/scala/esmeta/analyzer/domain/astValue/AstValueDomain.scala index 8c2949b1f2..0d2fc6c845 100644 --- a/src/main/scala/esmeta/analyzer/domain/astValue/Domain.scala +++ b/src/main/scala/esmeta/analyzer/domain/astValue/AstValueDomain.scala @@ -4,5 +4,8 @@ import esmeta.analyzer.* import esmeta.analyzer.domain.* import esmeta.state.AstValue -/** abstract AST domain */ -trait Domain extends domain.Domain[AstValue] +trait AstValueDomainDecl { self: Self => + + /** abstract AST domain */ + trait AstValueDomain extends Domain[AstValue] +} diff --git a/src/main/scala/esmeta/analyzer/domain/astValue/AstValueFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/astValue/AstValueFlatDomain.scala new file mode 100644 index 0000000000..3a332343f8 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/astValue/AstValueFlatDomain.scala @@ -0,0 +1,25 @@ +package esmeta.analyzer.domain.astValue + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait AstValueFlatDomainDecl { self: Self => + + /** flat domain for AST values */ + object AstValueFlatDomain + extends AstValueDomain + with FlatDomain[AstValue]("AST") { + + /** element interfaces */ + extension (elem: Elem) { + + /** join operator */ + override def ⊔(that: Elem): Elem = (elem, that) match + case (Bot, _) => that + case (_, Bot) => elem + case (Base(l), Base(r)) if l == r => elem + case _ => exploded(s"Merged AST value is not supported") + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/astValue/AstValueSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/astValue/AstValueSetDomain.scala new file mode 100644 index 0000000000..d1d9610626 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/astValue/AstValueSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.astValue + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait AstValueSetDomainDecl { self: Self => + + /** set domain for AST values */ + class AstValueSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends AstValueDomain + with SetDomain[AstValue]("AST", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/astValue/AstValueSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/astValue/AstValueSimpleDomain.scala new file mode 100644 index 0000000000..ac0ec1955d --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/astValue/AstValueSimpleDomain.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.astValue + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait AstValueSimpleDomainDecl { self: Self => + + /** simple domain for AST values */ + object AstValueSimpleDomain + extends AstValueDomain + with SimpleDomain[AstValue]("AST") +} diff --git a/src/main/scala/esmeta/analyzer/domain/astValue/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/astValue/FlatDomain.scala deleted file mode 100644 index 274c445851..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/astValue/FlatDomain.scala +++ /dev/null @@ -1,20 +0,0 @@ -package esmeta.analyzer.domain.astValue - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for AST values */ -object FlatDomain extends astValue.Domain with FlatDomain[AstValue]("AST") { - - /** element interfaces */ - extension (elem: Elem) { - - /** join operator */ - override def ⊔(that: Elem): Elem = (elem, that) match - case (Bot, _) => that - case (_, Bot) => elem - case (Base(l), Base(r)) if l == r => elem - case _ => exploded(s"Merged AST value is not supported") - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/astValue/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/astValue/SetDomain.scala deleted file mode 100644 index 2e5c7e920c..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/astValue/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.astValue - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for AST values */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends astValue.Domain - with domain.SetDomain[AstValue]("AST", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/astValue/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/astValue/SimpleDomain.scala deleted file mode 100644 index 85eed4a3e6..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/astValue/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.astValue - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for AST values */ -object SimpleDomain extends astValue.Domain with SimpleDomain[AstValue]("AST") diff --git a/src/main/scala/esmeta/analyzer/domain/astValue/package.scala b/src/main/scala/esmeta/analyzer/domain/astValue/package.scala new file mode 100644 index 0000000000..d25f3297de --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/astValue/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.astValue + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends AstValueDomainDecl + with AstValueSimpleDomainDecl + with AstValueFlatDomainDecl + with AstValueSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntDomain.scala b/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntDomain.scala new file mode 100644 index 0000000000..7cb33b286c --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.bigInt + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait BigIntDomainDecl { self: Self => + + /** abstract big integer domain */ + trait BigIntDomain extends Domain[BigInt] +} diff --git a/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntFlatDomain.scala new file mode 100644 index 0000000000..108faeccac --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntFlatDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.bigInt + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait BigIntFlatDomainDecl { self: Self => + + /** flat domain for big integer values */ + object BigIntFlatDomain extends BigIntDomain with FlatDomain[BigInt]("bigInt") +} diff --git a/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntSetDomain.scala new file mode 100644 index 0000000000..ab18e7895f --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.bigInt + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait BigIntSetDomainDecl { self: Self => + + /** set domain for big integer values */ + class BigIntSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends BigIntDomain + with SetDomain[BigInt]("bigInt", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntSimpleDomain.scala new file mode 100644 index 0000000000..c2fcad2eba --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/bigInt/BigIntSimpleDomain.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.bigInt + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait BigIntSimpleDomainDecl { self: Self => + + /** simple domain for big integer values */ + object BigIntSimpleDomain + extends BigIntDomain + with SimpleDomain[BigInt]("bigInt") +} diff --git a/src/main/scala/esmeta/analyzer/domain/bigInt/Domain.scala b/src/main/scala/esmeta/analyzer/domain/bigInt/Domain.scala deleted file mode 100644 index cd54222e6d..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/bigInt/Domain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.bigInt - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** abstract big integer domain */ -trait Domain extends domain.Domain[BigInt] diff --git a/src/main/scala/esmeta/analyzer/domain/bigInt/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/bigInt/FlatDomain.scala deleted file mode 100644 index bcb5c7b598..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/bigInt/FlatDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.bigInt - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for big integer values */ -object FlatDomain extends bigInt.Domain with FlatDomain[BigInt]("bigInt") diff --git a/src/main/scala/esmeta/analyzer/domain/bigInt/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/bigInt/SetDomain.scala deleted file mode 100644 index b8e3abc400..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/bigInt/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.bigInt - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for big integer values */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends bigInt.Domain - with domain.SetDomain[BigInt]("bigInt", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/bigInt/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/bigInt/SimpleDomain.scala deleted file mode 100644 index 3b9a7a5952..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/bigInt/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.bigInt - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for big integer values */ -object SimpleDomain extends bigInt.Domain with SimpleDomain[BigInt]("bigInt") diff --git a/src/main/scala/esmeta/analyzer/domain/bigInt/package.scala b/src/main/scala/esmeta/analyzer/domain/bigInt/package.scala new file mode 100644 index 0000000000..a2c0a50b64 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/bigInt/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.bigInt + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends BigIntDomainDecl + with BigIntSimpleDomainDecl + with BigIntFlatDomainDecl + with BigIntSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/bool/BoolDomain.scala b/src/main/scala/esmeta/analyzer/domain/bool/BoolDomain.scala new file mode 100644 index 0000000000..fb83b43f4a --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/bool/BoolDomain.scala @@ -0,0 +1,25 @@ +package esmeta.analyzer.domain.bool + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.Bool + +trait BoolDomainDecl { self: Self => + + /** abstract boolean domain */ + trait BoolDomain extends Domain[Bool] { + + /** boolean element interfaces */ + extension (elem: Elem) { + + /** unary negation */ + def unary_! : Elem + + /** logical OR */ + def ||(that: Elem): Elem + + /** logical AND */ + def &&(that: Elem): Elem + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/bool/BoolFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/bool/BoolFlatDomain.scala new file mode 100644 index 0000000000..5c75814838 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/bool/BoolFlatDomain.scala @@ -0,0 +1,37 @@ +package esmeta.analyzer.domain.bool + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.* + +trait BoolFlatDomainDecl { self: Self => + + /** flat domain for boolean values */ + object BoolFlatDomain + extends BoolDomain + with FlatDomain[Bool]("bool", Fin(T, F)) { + + /** interfaces */ + extension (elem: Elem) { + + /** unary negation */ + def unary_! : Elem = elem match + case Bot => Bot + case Top => Top + case Base(Bool(b)) => Base(Bool(!b)) + + /** logical OR */ + def ||(that: Elem): Elem = alpha(for { + Bool(l) <- elem + Bool(r) <- that + } yield Bool(l || r)) + + /** logical AND */ + def &&(that: Elem): Elem = alpha(for { + Bool(l) <- elem + Bool(r) <- that + } yield Bool(l && r)) + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/bool/Domain.scala b/src/main/scala/esmeta/analyzer/domain/bool/Domain.scala deleted file mode 100644 index 5f61cace4e..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/bool/Domain.scala +++ /dev/null @@ -1,22 +0,0 @@ -package esmeta.analyzer.domain.bool - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.Bool - -/** abstract boolean domain */ -trait Domain extends domain.Domain[Bool] { - - /** boolean element interfaces */ - extension (elem: Elem) { - - /** unary negation */ - def unary_! : Elem - - /** logical OR */ - def ||(that: Elem): Elem - - /** logical AND */ - def &&(that: Elem): Elem - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/bool/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/bool/FlatDomain.scala deleted file mode 100644 index 09b94fa461..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/bool/FlatDomain.scala +++ /dev/null @@ -1,34 +0,0 @@ -package esmeta.analyzer.domain.bool - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.* - -/** flat domain for boolean values */ -object FlatDomain - extends bool.Domain - with domain.FlatDomain[Bool]("bool", Fin(T, F)) { - - /** interfaces */ - extension (elem: Elem) { - - /** unary negation */ - def unary_! : Elem = elem match - case Bot => Bot - case Top => Top - case Base(Bool(b)) => Base(Bool(!b)) - - /** logical OR */ - def ||(that: Elem): Elem = alpha(for { - Bool(l) <- elem - Bool(r) <- that - } yield Bool(l || r)) - - /** logical AND */ - def &&(that: Elem): Elem = alpha(for { - Bool(l) <- elem - Bool(r) <- that - } yield Bool(l && r)) - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/bool/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/bool/SimpleDomain.scala deleted file mode 100644 index 3755f4ddfb..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/bool/SimpleDomain.scala +++ /dev/null @@ -1,29 +0,0 @@ -package esmeta.analyzer.domain.bool - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.* - -/** simple domain for boolean values */ -object SimpleDomain - extends bool.Domain - with domain.SimpleDomain[Bool]("bool", Fin(T, F)) { - - // interfaces - extension (elem: Elem) { - - /** unary negation */ - def unary_! : Elem = elem - - /** logical OR */ - def ||(that: Elem): Elem = (elem, that) match - case (Top, Top) => Top - case _ => Bot - - /** logical AND */ - def &&(that: Elem): Elem = (elem, that) match - case (Top, Top) => Top - case _ => Bot - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/bool/package.scala b/src/main/scala/esmeta/analyzer/domain/bool/package.scala new file mode 100644 index 0000000000..af618b6b8d --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/bool/package.scala @@ -0,0 +1,7 @@ +package esmeta.analyzer.domain.bool + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl extends BoolDomainDecl with BoolFlatDomainDecl { self: Self => } diff --git a/src/main/scala/esmeta/analyzer/domain/clo/CloDomain.scala b/src/main/scala/esmeta/analyzer/domain/clo/CloDomain.scala new file mode 100644 index 0000000000..b1a9b9638b --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/clo/CloDomain.scala @@ -0,0 +1,15 @@ +package esmeta.analyzer.domain.clo + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.Clo + +trait CloDomainDecl { self: Self => + + /** abstract closure domain */ + trait CloDomain extends Domain[AClo] { + + /** abstraction functions for an original closure */ + def alpha(clo: Clo): Elem = alpha(AClo.from(clo)) + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/clo/CloFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/clo/CloFlatDomain.scala new file mode 100644 index 0000000000..c9de62a3e1 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/clo/CloFlatDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.clo + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait CloFlatDomainDecl { self: Self => + + /** flat domain for closure values */ + object CloFlatDomain extends CloDomain with FlatDomain[AClo]("clo") +} diff --git a/src/main/scala/esmeta/analyzer/domain/clo/CloSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/clo/CloSetDomain.scala new file mode 100644 index 0000000000..8346dfcef1 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/clo/CloSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.clo + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait CloSetDomainDecl { self: Self => + + /** set domain for closure values */ + class CloSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends CloDomain + with SetDomain[AClo]("clo", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/clo/CloSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/clo/CloSimpleDomain.scala new file mode 100644 index 0000000000..2d4aa98d41 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/clo/CloSimpleDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.clo + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait CloSimpleDomainDecl { self: Self => + + /** simple domain for closure values */ + object CloSimpleDomain extends CloDomain with SimpleDomain[AClo]("clo") +} diff --git a/src/main/scala/esmeta/analyzer/domain/clo/Domain.scala b/src/main/scala/esmeta/analyzer/domain/clo/Domain.scala deleted file mode 100644 index 6f15df5c99..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/clo/Domain.scala +++ /dev/null @@ -1,12 +0,0 @@ -package esmeta.analyzer.domain.clo - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.Clo - -/** abstract closure domain */ -trait Domain extends domain.Domain[AClo] { - - /** abstraction functions for an original closure */ - def alpha(clo: Clo): Elem = alpha(AClo.from(clo)) -} diff --git a/src/main/scala/esmeta/analyzer/domain/clo/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/clo/FlatDomain.scala deleted file mode 100644 index 6e33fb2d6e..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/clo/FlatDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.clo - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for closure values */ -object FlatDomain extends clo.Domain with FlatDomain[AClo]("clo") diff --git a/src/main/scala/esmeta/analyzer/domain/clo/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/clo/SetDomain.scala deleted file mode 100644 index 87753102c4..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/clo/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.clo - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for closure values */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends clo.Domain - with domain.SetDomain[AClo]("clo", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/clo/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/clo/SimpleDomain.scala deleted file mode 100644 index e9ae0e603a..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/clo/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.clo - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for closure values */ -object SimpleDomain extends clo.Domain with SimpleDomain[AClo]("clo") diff --git a/src/main/scala/esmeta/analyzer/domain/clo/package.scala b/src/main/scala/esmeta/analyzer/domain/clo/package.scala new file mode 100644 index 0000000000..17b93650b3 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/clo/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.clo + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends CloDomainDecl + with CloSimpleDomainDecl + with CloFlatDomainDecl + with CloSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/codeUnit/Domain.scala b/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitDomain.scala similarity index 50% rename from src/main/scala/esmeta/analyzer/domain/codeUnit/Domain.scala rename to src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitDomain.scala index bee7ee8192..2b9b169fe1 100644 --- a/src/main/scala/esmeta/analyzer/domain/codeUnit/Domain.scala +++ b/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitDomain.scala @@ -4,5 +4,8 @@ import esmeta.analyzer.* import esmeta.analyzer.domain.* import esmeta.state.CodeUnit -/** abstract code unit domain */ -trait Domain extends domain.Domain[CodeUnit] +trait CodeUnitDomainDecl { self: Self => + + /** abstract code unit domain */ + trait CodeUnitDomain extends Domain[CodeUnit] +} diff --git a/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitFlatDomain.scala new file mode 100644 index 0000000000..67840a34e8 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitFlatDomain.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.codeUnit + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait CodeUnitFlatDomainDecl { self: Self => + + /** flat domain for code unit values */ + object CodeUnitFlatDomain + extends CodeUnitDomain + with FlatDomain[CodeUnit]("codeUnit") +} diff --git a/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitSetDomain.scala new file mode 100644 index 0000000000..9b665c820f --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.codeUnit + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait CodeUnitSetDomainDecl { self: Self => + + /** set domain for code unit values */ + class CodeUnitSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends CodeUnitDomain + with SetDomain[CodeUnit]("codeUnit", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitSimpleDomain.scala new file mode 100644 index 0000000000..0425744963 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/codeUnit/CodeUnitSimpleDomain.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.codeUnit + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait CodeUnitSimpleDomainDecl { self: Self => + + /** simple domain for code unit values */ + object CodeUnitSimpleDomain + extends CodeUnitDomain + with SimpleDomain[CodeUnit]("codeUnit") +} diff --git a/src/main/scala/esmeta/analyzer/domain/codeUnit/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/codeUnit/FlatDomain.scala deleted file mode 100644 index e1efa1f953..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/codeUnit/FlatDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.codeUnit - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for code unit values */ -object FlatDomain extends codeUnit.Domain with FlatDomain[CodeUnit]("codeUnit") diff --git a/src/main/scala/esmeta/analyzer/domain/codeUnit/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/codeUnit/SetDomain.scala deleted file mode 100644 index 6f364576e6..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/codeUnit/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.codeUnit - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for code unit values */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends codeUnit.Domain - with domain.SetDomain[CodeUnit]("codeUnit", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/codeUnit/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/codeUnit/SimpleDomain.scala deleted file mode 100644 index b4c9b0f7a8..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/codeUnit/SimpleDomain.scala +++ /dev/null @@ -1,10 +0,0 @@ -package esmeta.analyzer.domain.codeUnit - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for code unit values */ -object SimpleDomain - extends codeUnit.Domain - with SimpleDomain[CodeUnit]("codeUnit") diff --git a/src/main/scala/esmeta/analyzer/domain/codeUnit/package.scala b/src/main/scala/esmeta/analyzer/domain/codeUnit/package.scala new file mode 100644 index 0000000000..092697f963 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/codeUnit/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.codeUnit + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends CodeUnitDomainDecl + with CodeUnitSimpleDomainDecl + with CodeUnitFlatDomainDecl + with CodeUnitSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/comp/BasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/comp/BasicDomain.scala deleted file mode 100644 index 2cf84b4f33..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/comp/BasicDomain.scala +++ /dev/null @@ -1,143 +0,0 @@ -package esmeta.analyzer.domain.comp - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.* -import esmeta.util.Appender.* - -/** basic domain for completion records */ -object BasicDomain extends comp.Domain { - - /** elements */ - case class Elem(map: Map[String, Result] = Map()) extends Appendable - - /** top element */ - lazy val Top = exploded("top abstract completion record") - - /** bottom element */ - val Bot = Elem() - - /** abstraction functions */ - def alpha(xs: Iterable[AComp]): Elem = Elem( - (for (ty <- xs.map(_.ty)) - yield ty.name -> Result( - AbsPureValue(xs.filter(_.ty == ty).map(_.value)), - AbsPureValue(xs.filter(_.ty == ty).map(_.target.fold(Absent)(Str(_)))), - )).toMap, - ) - - /** abstraction functions */ - def alpha(ty: AbsValue, value: AbsValue, target: AbsValue): Elem = - val v = value.pureValue - val t = AbsPureValue(str = target.str, const = target.const) - ty.const.gamma match - case Inf => Top - case Fin(set) => - Elem((for (ty <- set) yield ty.name -> Result(v, t)).toMap) - - /** appender */ - given rule: Rule[Elem] = (app, elem) => - if (!elem.isBottom) - app >> elem.map.toList - .sortBy { case (t, _) => t } - .map { case (k, Result(v, t)) => s"~$k~ -> ($v, $t)" } - .mkString("{", ", ", "}") - else app >> "⊥" - - /** constructors with maps */ - def apply(map: Map[String, Result]): Elem = Elem(map) - - /** extractors */ - def unapply(elem: Elem): Product1[Map[String, Result]] = Tuple1(elem.map) - - /** element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = (elem, that) match - case _ if elem.isBottom => true - case _ if that.isBottom => false - case (Elem(lmap), Elem(rmap)) => - (lmap.keySet ++ rmap.keySet).forall(ty => { - elem.get(ty) ⊑ that.get(ty) - }) - - /** join operator */ - def ⊔(that: Elem): Elem = (elem, that) match - case _ if elem.isBottom => that - case _ if that.isBottom => elem - case (Elem(lmap), Elem(rmap)) => - val newMap = (lmap.keySet ++ rmap.keySet).toList - .map(ty => ty -> elem.get(ty) ⊔ that.get(ty)) - .toMap - Elem(newMap) - - /** meet operator */ - override def ⊓(that: Elem): Elem = (elem, that) match - case _ if elem.isBottom || that.isBottom => Bot - case (Elem(lmap), Elem(rmap)) => - val newPairs = (lmap.keySet ++ rmap.keySet).toList - .map(ty => ty -> elem.get(ty) ⊓ that.get(ty)) - .filter { case (_, res) => !res.isBottom } - Elem(newPairs.toMap) - - /** prune operator */ - override def --(that: Elem): Elem = (elem, that) match - case _ if elem ⊑ that => Bot - case _ if (elem ⊓ that).isBottom => elem - case (Elem(lmap), Elem(rmap)) => - val newPairs = for { - (ty, lres) <- lmap - rres = that.get(ty) - newRes = lres -- rres if !newRes.isBottom - } yield ty -> newRes - Elem(newPairs.toMap) - - /** normal completions */ - def normal: Result = get("normal") - - /** remove normal completions */ - def removeNormal: Elem = elem.copy(map = elem.map - "normal") - - /** result of each completion type */ - def get(ty: String): Result = - elem.map.getOrElse(ty, Result(AbsPureValue.Bot, AbsPureValue.Bot)) - - // get single value - override def getSingle: Flat[AComp] = - if (!elem.isBottom) elem.map.toList match - case List((ty, Result(value, target))) => - (value.getSingle, target.getSingle) match - case (One(v), One(t)) => - val tOpt = t match - case Str(str) => Some(str) - case _ => None - One(AComp(Const(ty), v, tOpt)) - case _ => Many - case _ => Many - else Zero - - /** merged result */ - def mergedResult: Result = - elem.map.map { case (_, v) => v }.foldLeft(Result.Bot)(_ ⊔ _) - - /** lookup */ - def apply(str: AbsStr): AbsPureValue = - var newV = AbsPureValue.Bot - val Result(value, target) = mergedResult - if (AbsStr(Str("Type")) ⊑ str) - newV ⊔= AbsPureValue(elem.map.keySet.map(Const(_))) - if (AbsStr(Str("Value")) ⊑ str) newV ⊔= value - if (AbsStr(Str("Target")) ⊑ str) newV ⊔= target - newV - - /** get reachable address partitions */ - def reachableParts: Set[Part] = - var parts = Set[Part]() - for ((_, Result(value, target)) <- elem.map) - parts ++= value.reachableParts - parts ++= target.reachableParts - parts - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/comp/CompBasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/comp/CompBasicDomain.scala new file mode 100644 index 0000000000..c32f0403be --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/comp/CompBasicDomain.scala @@ -0,0 +1,146 @@ +package esmeta.analyzer.domain.comp + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.* +import esmeta.util.Appender.* + +trait CompBasicDomainDecl { self: Self => + + /** basic domain for completion records */ + object CompBasicDomain extends CompDomain { + + /** elements */ + case class Elem(map: Map[String, Result] = Map()) extends Appendable + + /** top element */ + lazy val Top = exploded("top abstract completion record") + + /** bottom element */ + val Bot = Elem() + + /** abstraction functions */ + def alpha(xs: Iterable[AComp]): Elem = Elem( + (for (ty <- xs.map(_.ty)) + yield ty.name -> Result( + AbsPureValue(xs.filter(_.ty == ty).map(_.value)), + AbsPureValue(xs.filter(_.ty == ty).map(_.target.fold(Absent)(Str(_)))), + )).toMap, + ) + + /** abstraction functions */ + def alpha(ty: AbsValue, value: AbsValue, target: AbsValue): Elem = + val v = value.pureValue + val t = AbsPureValue(str = target.str, const = target.const) + ty.const.gamma match + case Inf => Top + case Fin(set) => + Elem((for (ty <- set) yield ty.name -> Result(v, t)).toMap) + + /** appender */ + given rule: Rule[Elem] = (app, elem) => + if (!elem.isBottom) + app >> elem.map.toList + .sortBy { case (t, _) => t } + .map { case (k, Result(v, t)) => s"~$k~ -> ($v, $t)" } + .mkString("{", ", ", "}") + else app >> "⊥" + + /** constructors with maps */ + def apply(map: Map[String, Result]): Elem = Elem(map) + + /** extractors */ + def unapply(elem: Elem): Product1[Map[String, Result]] = Tuple1(elem.map) + + /** element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = (elem, that) match + case _ if elem.isBottom => true + case _ if that.isBottom => false + case (Elem(lmap), Elem(rmap)) => + (lmap.keySet ++ rmap.keySet).forall(ty => { + elem.get(ty) ⊑ that.get(ty) + }) + + /** join operator */ + def ⊔(that: Elem): Elem = (elem, that) match + case _ if elem.isBottom => that + case _ if that.isBottom => elem + case (Elem(lmap), Elem(rmap)) => + val newMap = (lmap.keySet ++ rmap.keySet).toList + .map(ty => ty -> elem.get(ty) ⊔ that.get(ty)) + .toMap + Elem(newMap) + + /** meet operator */ + override def ⊓(that: Elem): Elem = (elem, that) match + case _ if elem.isBottom || that.isBottom => Bot + case (Elem(lmap), Elem(rmap)) => + val newPairs = (lmap.keySet ++ rmap.keySet).toList + .map(ty => ty -> elem.get(ty) ⊓ that.get(ty)) + .filter { case (_, res) => !res.isBottom } + Elem(newPairs.toMap) + + /** prune operator */ + override def --(that: Elem): Elem = (elem, that) match + case _ if elem ⊑ that => Bot + case _ if (elem ⊓ that).isBottom => elem + case (Elem(lmap), Elem(rmap)) => + val newPairs = for { + (ty, lres) <- lmap + rres = that.get(ty) + newRes = lres -- rres if !newRes.isBottom + } yield ty -> newRes + Elem(newPairs.toMap) + + /** normal completions */ + def normal: Result = get("normal") + + /** remove normal completions */ + def removeNormal: Elem = elem.copy(map = elem.map - "normal") + + /** result of each completion type */ + def get(ty: String): Result = + elem.map.getOrElse(ty, Result(AbsPureValue.Bot, AbsPureValue.Bot)) + + // get single value + override def getSingle: Flat[AComp] = + if (!elem.isBottom) elem.map.toList match + case List((ty, Result(value, target))) => + (value.getSingle, target.getSingle) match + case (One(v), One(t)) => + val tOpt = t match + case Str(str) => Some(str) + case _ => None + One(AComp(Const(ty), v, tOpt)) + case _ => Many + case _ => Many + else Zero + + /** merged result */ + def mergedResult: Result = + elem.map.map { case (_, v) => v }.foldLeft(Result.Bot)(_ ⊔ _) + + /** lookup */ + def apply(str: AbsStr): AbsPureValue = + var newV = AbsPureValue.Bot + val Result(value, target) = mergedResult + if (AbsStr(Str("Type")) ⊑ str) + newV ⊔= AbsPureValue(elem.map.keySet.map(Const(_))) + if (AbsStr(Str("Value")) ⊑ str) newV ⊔= value + if (AbsStr(Str("Target")) ⊑ str) newV ⊔= target + newV + + /** get reachable address partitions */ + def reachableParts: Set[Part] = + var parts = Set[Part]() + for ((_, Result(value, target)) <- elem.map) + parts ++= value.reachableParts + parts ++= target.reachableParts + parts + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/comp/CompDomain.scala b/src/main/scala/esmeta/analyzer/domain/comp/CompDomain.scala new file mode 100644 index 0000000000..9280883542 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/comp/CompDomain.scala @@ -0,0 +1,62 @@ +package esmeta.analyzer.domain.comp + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait CompDomainDecl { self: Self => + + /** abstract completion record domain */ + trait CompDomain extends Domain[AComp] { + + /** abstraction functions for an original completion value */ + def alpha(comp: Comp): Elem = alpha(AComp.from(comp)) + + /** abstraction functions */ + def alpha(ty: AbsValue, value: AbsValue, target: AbsValue): Elem + def apply(ty: AbsValue, value: AbsValue, target: AbsValue): Elem = + alpha(ty, value, target) + + /** constructors with maps */ + def apply(map: Map[String, Result]): Elem + + /** extractors */ + def unapply(elem: Elem): Product1[Map[String, Result]] + + /** results in completion records */ + case class Result(value: AbsPureValue, target: AbsPureValue) { + def isBottom = value.isBottom && target.isBottom + def ⊑(that: Result): Boolean = + this.value ⊑ that.value && this.target ⊑ that.target + def ⊔(that: Result): Result = + Result(this.value ⊔ that.value, this.target ⊔ that.target) + def ⊓(that: Result): Result = + Result(this.value ⊓ that.value, this.target ⊓ that.target) + def --(that: Result): Result = + Result(this.value -- that.value, this.target -- that.target) + } + object Result { val Bot = Result(AbsPureValue.Bot, AbsPureValue.Bot) } + + /** completion record element interfaces */ + extension (elem: Elem) { + + /** normal completions */ + def normal: Result + + /** remove normal completions */ + def removeNormal: Elem + + /** result of each completion type */ + def get(ty: String): Result + + /** merged result */ + def mergedResult: Result + + /** lookup */ + def apply(str: AbsStr): AbsPureValue + + /** get reachable address partitions */ + def reachableParts: Set[Part] + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/comp/Domain.scala b/src/main/scala/esmeta/analyzer/domain/comp/Domain.scala deleted file mode 100644 index c44764374e..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/comp/Domain.scala +++ /dev/null @@ -1,59 +0,0 @@ -package esmeta.analyzer.domain.comp - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** abstract completion record domain */ -trait Domain extends domain.Domain[AComp] { - - /** abstraction functions for an original completion value */ - def alpha(comp: Comp): Elem = alpha(AComp.from(comp)) - - /** abstraction functions */ - def alpha(ty: AbsValue, value: AbsValue, target: AbsValue): Elem - def apply(ty: AbsValue, value: AbsValue, target: AbsValue): Elem = - alpha(ty, value, target) - - /** constructors with maps */ - def apply(map: Map[String, Result]): Elem - - /** extractors */ - def unapply(elem: Elem): Product1[Map[String, Result]] - - /** results in completion records */ - case class Result(value: AbsPureValue, target: AbsPureValue) { - def isBottom = value.isBottom && target.isBottom - def ⊑(that: Result): Boolean = - this.value ⊑ that.value && this.target ⊑ that.target - def ⊔(that: Result): Result = - Result(this.value ⊔ that.value, this.target ⊔ that.target) - def ⊓(that: Result): Result = - Result(this.value ⊓ that.value, this.target ⊓ that.target) - def --(that: Result): Result = - Result(this.value -- that.value, this.target -- that.target) - } - object Result { val Bot = Result(AbsPureValue.Bot, AbsPureValue.Bot) } - - /** completion record element interfaces */ - extension (elem: Elem) { - - /** normal completions */ - def normal: Result - - /** remove normal completions */ - def removeNormal: Elem - - /** result of each completion type */ - def get(ty: String): Result - - /** merged result */ - def mergedResult: Result - - /** lookup */ - def apply(str: AbsStr): AbsPureValue - - /** get reachable address partitions */ - def reachableParts: Set[Part] - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/comp/package.scala b/src/main/scala/esmeta/analyzer/domain/comp/package.scala new file mode 100644 index 0000000000..eff4e288af --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/comp/package.scala @@ -0,0 +1,7 @@ +package esmeta.analyzer.domain.comp + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl extends CompDomainDecl with CompBasicDomainDecl { self: Self => } diff --git a/src/main/scala/esmeta/analyzer/domain/const/Domain.scala b/src/main/scala/esmeta/analyzer/domain/const/ConstDomain.scala similarity index 51% rename from src/main/scala/esmeta/analyzer/domain/const/Domain.scala rename to src/main/scala/esmeta/analyzer/domain/const/ConstDomain.scala index 83beac511e..e5998a1198 100644 --- a/src/main/scala/esmeta/analyzer/domain/const/Domain.scala +++ b/src/main/scala/esmeta/analyzer/domain/const/ConstDomain.scala @@ -4,5 +4,8 @@ import esmeta.analyzer.* import esmeta.analyzer.domain.* import esmeta.state.Const -/** abstract constant domain */ -trait Domain extends domain.Domain[Const] +trait ConstDomainDecl { self: Self => + + /** abstract constant domain */ + trait ConstDomain extends Domain[Const] +} diff --git a/src/main/scala/esmeta/analyzer/domain/const/ConstFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/const/ConstFlatDomain.scala new file mode 100644 index 0000000000..447b723dea --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/const/ConstFlatDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.const + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait ConstFlatDomainDecl { self: Self => + + /** flat domain for constant values */ + object ConstFlatDomain extends ConstDomain with FlatDomain[Const]("const") +} diff --git a/src/main/scala/esmeta/analyzer/domain/const/ConstSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/const/ConstSetDomain.scala new file mode 100644 index 0000000000..74f7d7db64 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/const/ConstSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.const + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait ConstSetDomainDecl { self: Self => + + /** set domain for constant values */ + class ConstSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends ConstDomain + with SetDomain[Const]("const", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/const/ConstSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/const/ConstSimpleDomain.scala new file mode 100644 index 0000000000..a35707cc25 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/const/ConstSimpleDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.const + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait ConstSimpleDomainDecl { self: Self => + + /** simple domain for constant values */ + object ConstSimpleDomain extends ConstDomain with SimpleDomain[Const]("const") +} diff --git a/src/main/scala/esmeta/analyzer/domain/const/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/const/FlatDomain.scala deleted file mode 100644 index 6ac99a163d..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/const/FlatDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.const - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for constant values */ -object FlatDomain extends const.Domain with FlatDomain[Const]("const") diff --git a/src/main/scala/esmeta/analyzer/domain/const/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/const/SetDomain.scala deleted file mode 100644 index 05ad3bcc85..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/const/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.const - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for constant values */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends const.Domain - with domain.SetDomain[Const]("const", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/const/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/const/SimpleDomain.scala deleted file mode 100644 index 194fe26313..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/const/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.const - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for constant values */ -object SimpleDomain extends const.Domain with SimpleDomain[Const]("const") diff --git a/src/main/scala/esmeta/analyzer/domain/const/package.scala b/src/main/scala/esmeta/analyzer/domain/const/package.scala new file mode 100644 index 0000000000..9442ba30fa --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/const/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.const + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends ConstDomainDecl + with ConstSimpleDomainDecl + with ConstFlatDomainDecl + with ConstSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/cont/ContDomain.scala b/src/main/scala/esmeta/analyzer/domain/cont/ContDomain.scala new file mode 100644 index 0000000000..89496f163f --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/cont/ContDomain.scala @@ -0,0 +1,15 @@ +package esmeta.analyzer.domain.cont + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.Cont + +trait ContDomainDecl { self: Self => + + /** abstract continuation domain */ + trait ContDomain extends Domain[ACont] { + + /** abstraction functions for an original continuation */ + def alpha(cont: Cont): Elem = alpha(ACont.from(cont)) + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/cont/ContFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/cont/ContFlatDomain.scala new file mode 100644 index 0000000000..445376fd1f --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/cont/ContFlatDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.cont + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait ContFlatDomainDecl { self: Self => + + /** flat domain for continuation values */ + object ContFlatDomain extends ContDomain with FlatDomain[ACont]("cont") +} diff --git a/src/main/scala/esmeta/analyzer/domain/cont/ContSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/cont/ContSetDomain.scala new file mode 100644 index 0000000000..c5ac5ee9b2 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/cont/ContSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.cont + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait ContSetDomainDecl { self: Self => + + /** set domain for continuation values */ + class ContSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends ContDomain + with SetDomain[ACont]("cont", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/cont/ContSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/cont/ContSimpleDomain.scala new file mode 100644 index 0000000000..7c72bba5cc --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/cont/ContSimpleDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.cont + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait ContSimpleDomainDecl { self: Self => + + /** simple domain for continuation values */ + object ContSimpleDomain extends ContDomain with SimpleDomain[ACont]("cont") +} diff --git a/src/main/scala/esmeta/analyzer/domain/cont/Domain.scala b/src/main/scala/esmeta/analyzer/domain/cont/Domain.scala deleted file mode 100644 index 9e4e3e48c0..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/cont/Domain.scala +++ /dev/null @@ -1,12 +0,0 @@ -package esmeta.analyzer.domain.cont - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.Cont - -/** abstract continuation domain */ -trait Domain extends domain.Domain[ACont] { - - /** abstraction functions for an original continuation */ - def alpha(cont: Cont): Elem = alpha(ACont.from(cont)) -} diff --git a/src/main/scala/esmeta/analyzer/domain/cont/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/cont/FlatDomain.scala deleted file mode 100644 index 3002da3741..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/cont/FlatDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.cont - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for continuation values */ -object FlatDomain extends cont.Domain with FlatDomain[ACont]("cont") diff --git a/src/main/scala/esmeta/analyzer/domain/cont/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/cont/SetDomain.scala deleted file mode 100644 index fbbd8be1a5..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/cont/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.cont - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for continuation values */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends cont.Domain - with domain.SetDomain[ACont]("cont", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/cont/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/cont/SimpleDomain.scala deleted file mode 100644 index 91793a6d75..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/cont/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.cont - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for continuation values */ -object SimpleDomain extends cont.Domain with SimpleDomain[ACont]("cont") diff --git a/src/main/scala/esmeta/analyzer/domain/cont/package.scala b/src/main/scala/esmeta/analyzer/domain/cont/package.scala new file mode 100644 index 0000000000..341a071831 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/cont/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.cont + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends ContDomainDecl + with ContSimpleDomainDecl + with ContFlatDomainDecl + with ContSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/heap/BasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/heap/BasicDomain.scala deleted file mode 100644 index 1093a4e813..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/heap/BasicDomain.scala +++ /dev/null @@ -1,295 +0,0 @@ -package esmeta.analyzer.domain.heap - -import esmeta.LINE_SEP -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.cfg.* -import esmeta.es -import esmeta.ir.* -import esmeta.state.* -import esmeta.util.* -import esmeta.util.Appender -import esmeta.util.Appender.{*, given} -import esmeta.util.BaseUtils.* - -/** basic domain for heaps */ -object BasicDomain extends heap.Domain { - - /** elements */ - case class Elem(map: Map[Part, AbsObj], merged: Set[Part]) extends Appendable - - /** top element */ - lazy val Top: Elem = exploded("top abstract heap") - - /** bottom element */ - val Bot: Elem = Elem(Map(), Set()) - - /** abstraction functions */ - def alpha(xs: Iterable[Heap]): Elem = Top - - /** constructors */ - def apply( - map: Map[Part, AbsObj] = Map(), - merged: Set[Part] = Set(), - ): Elem = Elem(map, merged) - - /** extractors */ - def unapply(elem: Elem): (Map[Part, AbsObj], Set[Part]) = - (elem.map, elem.merged) - - /** appender */ - given rule: Rule[Elem] = mkRule(true) - - /** simpler appender */ - val shortRule: Rule[Elem] = mkRule(false) - - /** set bases */ - def setBase(heap: Heap): Unit = base = (for { - (addr, obj) <- heap.map - part = Part.from(addr) - aobj = AbsObj(obj) - } yield part -> aobj).toMap - private var base: Map[Part, AbsObj] = Map() - - /** element interfaces */ - extension (elem: Elem) { - - /** bottom check */ - override def isBottom = elem.map.isEmpty - - /** partial order */ - def ⊑(that: Elem): Boolean = (elem, that) match - case _ if elem.isBottom => true - case _ if that.isBottom => false - case (l, r) => ( - (l.map.keySet ++ r.map.keySet).forall(part => { - elem(part) ⊑ that(part) - }) && (l.merged subsetOf r.merged) - ) - - /** join operator */ - def ⊔(that: Elem): Elem = (elem, that) match - case _ if elem.isBottom => that - case _ if that.isBottom => elem - case (l, r) => - Elem( - map = (l.map.keySet ++ r.map.keySet).toList - .map(part => { - part -> elem(part) ⊔ that(part) - }) - .toMap, - merged = l.merged ++ r.merged, - ) - - /** join operator */ - override def ⊓(that: Elem): Elem = (elem, that) match - case _ if elem.isBottom || that.isBottom => Bot - case (Elem(lmap, lmerged), relem @ Elem(rmap, rmerged)) => - val newMap = for { - (part, lobj) <- lmap - newObj = lobj ⊓ relem(part) if !newObj.isBottom - } yield part -> newObj - if (newMap.isEmpty) Bot - else - Elem( - map = newMap.toMap, - merged = lmerged intersect rmerged, - ) - - /** singleton checks */ - def isSingle: Boolean = - elem.map.forall { case (part, obj) => isSingle(part) && obj.isSingle } - - /** singleton address partition checks */ - def isSingle(apart: AbsPart): Boolean = apart.getSingle match - case One(part) => isSingle(part) - case _ => false - def isSingle(part: Part): Boolean = !(elem.merged contains part) - - /** handle calls */ - def doCall: Elem = elem - def doProcStart(fixed: Set[Part]): Elem = elem - - /** handle returns (elem: caller heaps / retHeap: return heaps) */ - def doReturn(to: Elem): Elem = elem - def doProcEnd(to: Elem): Elem = elem - - /** get reachable address partitions */ - def reachableParts(initParts: Set[Part]): Set[Part] = - var visited = Set[Part]() - var reached = Set[Part]() - def aux(part: Part): Unit = if (!visited.contains(part)) { - visited += part - if (!part.isNamed) reached += part - elem(part).reachableParts.filter(!_.isNamed).foreach(aux) - } - elem.map.keys.filter(_.isNamed).foreach(aux) - initParts.filter(!_.isNamed).foreach(aux) - reached - - /** remove given address partitions */ - def removeParts(parts: Part*): Elem = elem - def removeParts(parts: Set[Part]): Elem = elem - - /** lookup abstract address partitions */ - def apply(part: Part): AbsObj = - elem.map.getOrElse(part, base.getOrElse(part, AbsObj.Bot)) - def apply(part: AbsPart, prop: AbsValue): AbsValue = - part.map(elem(_, prop)).foldLeft(AbsValue.Bot: AbsValue)(_ ⊔ _) - def apply(part: Part, prop: AbsValue): AbsValue = part match - case Named(es.builtin.INTRINSICS) => - prop.getSingle match - case Zero => AbsValue.Bot - case One(str: SimpleValue) => - AbsValue(Heap.getIntrinsics(str)) - case One(_) => AbsValue.Bot - case Many => - AbsValue.Top - case _ => elem(part).get(prop) - - /** setters */ - def update(part: AbsPart, prop: AbsValue, value: AbsValue): Elem = - applyEach(elem, part)(_.update(prop, value, _)) - - /** delete */ - def delete(part: AbsPart, prop: AbsValue): Elem = - applyEach(elem, part)(_.delete(prop, _)) - - /** concat */ - def concat(part: AbsPart, value: AbsValue): Elem = - val obj = value.part.foldLeft[AbsObj](AbsObj.Bot) { - case (obj, part) => obj ⊔ apply(part) - } - applyEach(elem, part)(_.concat(obj, _)) - - /** append */ - def append(part: AbsPart, value: AbsValue): Elem = - applyEach(elem, part)(_.append(value, _)) - - /** prepend */ - def prepend(part: AbsPart, value: AbsValue): Elem = - applyEach(elem, part)(_.prepend(value, _)) - - /** pops */ - def pop(part: AbsPart, front: Boolean): (AbsValue, Elem) = - var v: AbsValue = AbsValue.Bot - val h: Elem = applyEach(elem, part)((obj, weak) => { - val (newV, newObj) = obj.pop(weak, front) - v ⊔= newV - newObj - }) - (v, h) - - /** remove */ - def remove(part: AbsPart, value: AbsValue): Elem = - applyEach(elem, part)(_.remove(value, _)) - - /** copy objects */ - def copyObj(from: AbsPart)(to: AllocSite): Elem = - alloc(elem, to, applyFold(elem, from)(obj => obj)) - - /** keys of map */ - def keys(part: AbsPart, intSorted: Boolean)(to: AllocSite): Elem = - alloc(elem, to, applyFold(elem, part)(_.keys(intSorted))) - - /** has SubMap */ - def hasSubMap(tname: String): Boolean = - (tname endsWith "Object") || (tname endsWith "EnvironmentRecord") - - /** allocation of map with address partitions */ - def allocMap( - to: AllocSite, - tname: String, - pairs: Iterable[(AbsValue, AbsValue)], - ): Elem = - given CFG = cfg - val newObj = pairs.foldLeft(AbsObj(MapObj(tname))) { - case (m, (k, v)) => m.update(k, v, weak = false) - } - if (hasSubMap(tname)) { - val subMapPart = SubMap(to) - val subMapObj = AbsObj(MapObj("SubMap")) - val newElem = alloc( - elem, - to, - newObj.update(AbsValue("SubMap"), AbsValue(subMapPart), weak = false), - ) - alloc(newElem, subMapPart, subMapObj) - } else alloc(elem, to, newObj) - - /** allocation of list with address partitions */ - def allocList( - to: AllocSite, - values: Iterable[AbsValue], - ): Elem = alloc(elem, to, AbsObj.getList(values)) - - /** allocation of symbol with address partitions */ - def allocSymbol(to: AllocSite, desc: AbsValue): Elem = - alloc(elem, to, AbsObj.getSymbol(desc)) - - /** set type of objects */ - def setType(part: AbsPart, tname: String): Elem = - applyEach(elem, part)((obj, _) => obj.setType(tname)) - - /** check contains */ - def contains(part: AbsPart, value: AbsValue): AbsValue = - part.toList.foldLeft(AbsValue.Bot: AbsValue) { - case (bool, part) => bool ⊔ (elem(part) contains value) - } - - /** conversion to string */ - def toString(detail: Boolean): String = - val app = Appender() - given heapRule: Rule[Elem] = if (detail) rule else shortRule - app >> elem - app.toString - } - - // --------------------------------------------------------------------------- - // private helpers - // --------------------------------------------------------------------------- - // appender generator - private def mkRule(detail: Boolean): Rule[Elem] = (app, elem) => - val Elem(map, merged) = elem - if (elem.isBottom) app >> "{}" - else if (!detail) app >> "{ ... }" - else - app.wrap { - map.toList - .sortBy(_._1.toString) - .foreach { - case (k, v) => - app :> "[" - app >> (if (merged contains k) "M" else " ") - app >> "] " >> s"$k -> " >> v >> LINE_SEP - } - } - - // helper for abstract address partitions - private def applyEach(elem: Elem, part: AbsPart)( - f: (AbsObj, Boolean) => AbsObj, - ): Elem = - val weak = !elem.isSingle(part) - part.toList.foldLeft(elem) { - case (heap, part) => - val obj = heap(part) - val newObj = f(obj, weak) - Elem( - map = heap.map + (part -> newObj), - elem.merged, - ) - } - - // helper for abstract address partitions - private def applyFold(elem: Elem, part: AbsPart)( - f: AbsObj => AbsObj, - ): AbsObj = part.toList.foldLeft(AbsObj.Bot: AbsObj) { - case (obj, part) => obj ⊔ f(elem(part)) - } - - // allocation helper - private def alloc(elem: Elem, part: Part, obj: AbsObj): Elem = - val cur = elem(part) - if (cur.isBottom) Elem(elem.map + (part -> obj), elem.merged) - else Elem(elem.map + (part -> (cur ⊔ obj)), elem.merged + part) -} diff --git a/src/main/scala/esmeta/analyzer/domain/heap/Domain.scala b/src/main/scala/esmeta/analyzer/domain/heap/Domain.scala deleted file mode 100644 index 0f2a0d3044..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/heap/Domain.scala +++ /dev/null @@ -1,108 +0,0 @@ -package esmeta.analyzer.domain.heap - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.Appender.* - -/** abstract heap domain */ -trait Domain extends domain.Domain[Heap] { - - /** constructors */ - def apply(map: Map[Part, AbsObj] = Map(), merged: Set[Part] = Set()): Elem - - /** extractors */ - def unapply(elem: Elem): (Map[Part, AbsObj], Set[Part]) - - /** set bases */ - def setBase(heap: Heap): Unit - - /** simpler appender */ - val shortRule: Rule[Elem] - - /** abstract heap interfaces */ - extension (elem: Elem) { - - /** singleton checks */ - def isSingle: Boolean - - /** singleton address partition checks */ - def isSingle(apart: AbsPart): Boolean - def isSingle(part: Part): Boolean - - /** handle calls */ - def doCall: Elem - def doProcStart(fixed: Set[Part]): Elem - - /** handle returns (this: caller heaps / retHeap: return heaps) */ - def doReturn(to: Elem): Elem - def doProcEnd(to: Elem): Elem - - /** get reachable address partitions */ - def reachableParts(initParts: Set[Part]): Set[Part] - - /** remove given address partitions */ - def removeParts(parts: Part*): Elem - def removeParts(parts: Set[Part]): Elem - - /** lookup abstract address partitions */ - def apply(part: Part): AbsObj - def apply(part: AbsPart, prop: AbsValue): AbsValue - def apply(part: Part, prop: AbsValue): AbsValue - - /** setters */ - def update(part: AbsPart, prop: AbsValue, value: AbsValue): Elem - - /** delete */ - def delete(part: AbsPart, prop: AbsValue): Elem - - /** concat */ - def concat(part: AbsPart, value: AbsValue): Elem - - /** appends */ - def append(part: AbsPart, value: AbsValue): Elem - - /** prepends */ - def prepend(part: AbsPart, value: AbsValue): Elem - - /** pops */ - def pop(part: AbsPart, front: Boolean): (AbsValue, Elem) - - /** remove */ - def remove(part: AbsPart, value: AbsValue): Elem - - /** copy objects */ - def copyObj(from: AbsPart)(to: AllocSite): Elem - - /** keys of map */ - def keys(part: AbsPart, intSorted: Boolean)(to: AllocSite): Elem - - /** has SubMap */ - def hasSubMap(tname: String): Boolean - - /** allocation of map with address partitions */ - def allocMap( - to: AllocSite, - tname: String, - pairs: Iterable[(AbsValue, AbsValue)] = Nil, - ): Elem - - /** allocation of list with address partitions */ - def allocList( - to: AllocSite, - values: Iterable[AbsValue] = Nil, - ): Elem - - /** allocation of symbol with address partitions */ - def allocSymbol(to: AllocSite, desc: AbsValue): Elem - - /** set type of objects */ - def setType(part: AbsPart, tname: String): Elem - - /** check contains */ - def contains(part: AbsPart, value: AbsValue): AbsValue - - /** conversion to string */ - def toString(detail: Boolean): String - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/heap/HeapBasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/heap/HeapBasicDomain.scala new file mode 100644 index 0000000000..48f926eb81 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/heap/HeapBasicDomain.scala @@ -0,0 +1,303 @@ +package esmeta.analyzer.domain.heap + +import esmeta.LINE_SEP +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.cfg.* +import esmeta.es +import esmeta.ir.* +import esmeta.state.* +import esmeta.util.* +import esmeta.util.Appender +import esmeta.util.Appender.{*, given} +import esmeta.util.BaseUtils.* + +trait HeapBasicDomainDecl { self: Self => + + /** basic domain for heaps */ + object HeapBasicDomain extends HeapDomain { + + /** elements */ + case class Elem(map: Map[Part, AbsObj], merged: Set[Part]) + extends Appendable + + /** top element */ + lazy val Top: Elem = exploded("top abstract heap") + + /** bottom element */ + val Bot: Elem = Elem(Map(), Set()) + + /** abstraction functions */ + def alpha(xs: Iterable[Heap]): Elem = Top + + /** constructors */ + def apply( + map: Map[Part, AbsObj] = Map(), + merged: Set[Part] = Set(), + ): Elem = Elem(map, merged) + + /** extractors */ + def unapply(elem: Elem): (Map[Part, AbsObj], Set[Part]) = + (elem.map, elem.merged) + + /** appender */ + given rule: Rule[Elem] = mkRule(true) + + /** simpler appender */ + val shortRule: Rule[Elem] = mkRule(false) + + /** set bases */ + def setBase(heap: Heap): Unit = base = (for { + (addr, obj) <- heap.map + part = Part.from(addr) + aobj = AbsObj(obj) + } yield part -> aobj).toMap + private var base: Map[Part, AbsObj] = Map() + + /** element interfaces */ + extension (elem: Elem) { + + /** bottom check */ + override def isBottom = elem.map.isEmpty + + /** partial order */ + def ⊑(that: Elem): Boolean = (elem, that) match + case _ if elem.isBottom => true + case _ if that.isBottom => false + case (l, r) => ( + (l.map.keySet ++ r.map.keySet).forall(part => { + elem(part) ⊑ that(part) + }) && (l.merged subsetOf r.merged) + ) + + /** join operator */ + def ⊔(that: Elem): Elem = (elem, that) match + case _ if elem.isBottom => that + case _ if that.isBottom => elem + case (l, r) => + Elem( + map = (l.map.keySet ++ r.map.keySet).toList + .map(part => { + part -> elem(part) ⊔ that(part) + }) + .toMap, + merged = l.merged ++ r.merged, + ) + + /** join operator */ + override def ⊓(that: Elem): Elem = (elem, that) match + case _ if elem.isBottom || that.isBottom => Bot + case (Elem(lmap, lmerged), relem @ Elem(rmap, rmerged)) => + val newMap = for { + (part, lobj) <- lmap + newObj = lobj ⊓ relem(part) if !newObj.isBottom + } yield part -> newObj + if (newMap.isEmpty) Bot + else + Elem( + map = newMap.toMap, + merged = lmerged intersect rmerged, + ) + + /** singleton checks */ + def isSingle: Boolean = + elem.map.forall { case (part, obj) => isSingle(part) && obj.isSingle } + + /** singleton address partition checks */ + def isSingle(apart: AbsPart): Boolean = apart.getSingle match + case One(part) => isSingle(part) + case _ => false + def isSingle(part: Part): Boolean = !(elem.merged contains part) + + /** handle calls */ + def doCall: Elem = elem + def doProcStart(fixed: Set[Part]): Elem = elem + + /** handle returns (elem: caller heaps / retHeap: return heaps) */ + def doReturn(to: Elem): Elem = elem + def doProcEnd(to: Elem): Elem = elem + + /** get reachable address partitions */ + def reachableParts(initParts: Set[Part]): Set[Part] = + var visited = Set[Part]() + var reached = Set[Part]() + def aux(part: Part): Unit = if (!visited.contains(part)) { + visited += part + if (!part.isNamed) reached += part + elem(part).reachableParts.filter(!_.isNamed).foreach(aux) + } + elem.map.keys.filter(_.isNamed).foreach(aux) + initParts.filter(!_.isNamed).foreach(aux) + reached + + /** remove given address partitions */ + def removeParts(parts: Part*): Elem = elem + def removeParts(parts: Set[Part]): Elem = elem + + /** lookup abstract address partitions */ + def apply(part: Part): AbsObj = + elem.map.getOrElse(part, base.getOrElse(part, AbsObj.Bot)) + def apply(part: AbsPart, prop: AbsValue): AbsValue = + part.map(elem(_, prop)).foldLeft(AbsValue.Bot: AbsValue)(_ ⊔ _) + def apply(part: Part, prop: AbsValue): AbsValue = part match + case Named(es.builtin.INTRINSICS) => + prop.getSingle match + case Zero => AbsValue.Bot + case One(str: SimpleValue) => + AbsValue(Heap.getIntrinsics(str)) + case One(_) => AbsValue.Bot + case Many => + AbsValue.Top + case _ => elem(part).get(prop) + + /** setters */ + def update(part: AbsPart, prop: AbsValue, value: AbsValue): Elem = + applyEach(elem, part)(_.update(prop, value, _)) + + /** delete */ + def delete(part: AbsPart, prop: AbsValue): Elem = + applyEach(elem, part)(_.delete(prop, _)) + + /** concat */ + def concat(part: AbsPart, value: AbsValue): Elem = + val obj = value.part.foldLeft[AbsObj](AbsObj.Bot) { + case (obj, part) => obj ⊔ apply(part) + } + applyEach(elem, part)(_.concat(obj, _)) + + /** append */ + def append(part: AbsPart, value: AbsValue): Elem = + applyEach(elem, part)(_.append(value, _)) + + /** prepend */ + def prepend(part: AbsPart, value: AbsValue): Elem = + applyEach(elem, part)(_.prepend(value, _)) + + /** pops */ + def pop(part: AbsPart, front: Boolean): (AbsValue, Elem) = + var v: AbsValue = AbsValue.Bot + val h: Elem = applyEach(elem, part)((obj, weak) => { + val (newV, newObj) = obj.pop(weak, front) + v ⊔= newV + newObj + }) + (v, h) + + /** remove */ + def remove(part: AbsPart, value: AbsValue): Elem = + applyEach(elem, part)(_.remove(value, _)) + + /** copy objects */ + def copyObj(from: AbsPart)(to: AllocSite): Elem = + alloc(elem, to, applyFold(elem, from)(obj => obj)) + + /** keys of map */ + def keys(part: AbsPart, intSorted: Boolean)(to: AllocSite): Elem = + alloc(elem, to, applyFold(elem, part)(_.keys(intSorted))) + + /** has SubMap */ + def hasSubMap(tname: String): Boolean = + (tname endsWith "Object") || (tname endsWith "EnvironmentRecord") + + /** allocation of map with address partitions */ + def allocMap( + to: AllocSite, + tname: String, + pairs: Iterable[(AbsValue, AbsValue)], + ): Elem = + given CFG = cfg + val newObj = pairs.foldLeft(AbsObj(MapObj(tname))) { + case (m, (k, v)) => m.update(k, v, weak = false) + } + if (hasSubMap(tname)) { + val subMapPart = SubMap(to) + val subMapObj = AbsObj(MapObj("SubMap")) + val newElem = alloc( + elem, + to, + newObj.update( + AbsValue("SubMap"), + AbsValue(subMapPart), + weak = false, + ), + ) + alloc(newElem, subMapPart, subMapObj) + } else alloc(elem, to, newObj) + + /** allocation of list with address partitions */ + def allocList( + to: AllocSite, + values: Iterable[AbsValue], + ): Elem = alloc(elem, to, AbsObj.getList(values)) + + /** allocation of symbol with address partitions */ + def allocSymbol(to: AllocSite, desc: AbsValue): Elem = + alloc(elem, to, AbsObj.getSymbol(desc)) + + /** set type of objects */ + def setType(part: AbsPart, tname: String): Elem = + applyEach(elem, part)((obj, _) => obj.setType(tname)) + + /** check contains */ + def contains(part: AbsPart, value: AbsValue): AbsValue = + part.toList.foldLeft(AbsValue.Bot: AbsValue) { + case (bool, part) => bool ⊔ (elem(part) contains value) + } + + /** conversion to string */ + def toString(detail: Boolean): String = + val app = Appender() + given heapRule: Rule[Elem] = if (detail) rule else shortRule + app >> elem + app.toString + } + + // ------------------------------------------------------------------------- + // private helpers + // ------------------------------------------------------------------------- + // appender generator + private def mkRule(detail: Boolean): Rule[Elem] = (app, elem) => + val Elem(map, merged) = elem + if (elem.isBottom) app >> "{}" + else if (!detail) app >> "{ ... }" + else + app.wrap { + map.toList + .sortBy(_._1.toString) + .foreach { + case (k, v) => + app :> "[" + app >> (if (merged contains k) "M" else " ") + app >> "] " >> s"$k -> " >> v >> LINE_SEP + } + } + + // helper for abstract address partitions + private def applyEach(elem: Elem, part: AbsPart)( + f: (AbsObj, Boolean) => AbsObj, + ): Elem = + val weak = !elem.isSingle(part) + part.toList.foldLeft(elem) { + case (heap, part) => + val obj = heap(part) + val newObj = f(obj, weak) + Elem( + map = heap.map + (part -> newObj), + elem.merged, + ) + } + + // helper for abstract address partitions + private def applyFold(elem: Elem, part: AbsPart)( + f: AbsObj => AbsObj, + ): AbsObj = part.toList.foldLeft(AbsObj.Bot: AbsObj) { + case (obj, part) => obj ⊔ f(elem(part)) + } + + // allocation helper + private def alloc(elem: Elem, part: Part, obj: AbsObj): Elem = + val cur = elem(part) + if (cur.isBottom) Elem(elem.map + (part -> obj), elem.merged) + else Elem(elem.map + (part -> (cur ⊔ obj)), elem.merged + part) + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/heap/HeapDomain.scala b/src/main/scala/esmeta/analyzer/domain/heap/HeapDomain.scala new file mode 100644 index 0000000000..cbb48df1dd --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/heap/HeapDomain.scala @@ -0,0 +1,111 @@ +package esmeta.analyzer.domain.heap + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.Appender.* + +trait HeapDomainDecl { self: Self => + + /** abstract heap domain */ + trait HeapDomain extends Domain[Heap] { + + /** constructors */ + def apply(map: Map[Part, AbsObj] = Map(), merged: Set[Part] = Set()): Elem + + /** extractors */ + def unapply(elem: Elem): (Map[Part, AbsObj], Set[Part]) + + /** set bases */ + def setBase(heap: Heap): Unit + + /** simpler appender */ + val shortRule: Rule[Elem] + + /** abstract heap interfaces */ + extension (elem: Elem) { + + /** singleton checks */ + def isSingle: Boolean + + /** singleton address partition checks */ + def isSingle(apart: AbsPart): Boolean + def isSingle(part: Part): Boolean + + /** handle calls */ + def doCall: Elem + def doProcStart(fixed: Set[Part]): Elem + + /** handle returns (this: caller heaps / retHeap: return heaps) */ + def doReturn(to: Elem): Elem + def doProcEnd(to: Elem): Elem + + /** get reachable address partitions */ + def reachableParts(initParts: Set[Part]): Set[Part] + + /** remove given address partitions */ + def removeParts(parts: Part*): Elem + def removeParts(parts: Set[Part]): Elem + + /** lookup abstract address partitions */ + def apply(part: Part): AbsObj + def apply(part: AbsPart, prop: AbsValue): AbsValue + def apply(part: Part, prop: AbsValue): AbsValue + + /** setters */ + def update(part: AbsPart, prop: AbsValue, value: AbsValue): Elem + + /** delete */ + def delete(part: AbsPart, prop: AbsValue): Elem + + /** concat */ + def concat(part: AbsPart, value: AbsValue): Elem + + /** appends */ + def append(part: AbsPart, value: AbsValue): Elem + + /** prepends */ + def prepend(part: AbsPart, value: AbsValue): Elem + + /** pops */ + def pop(part: AbsPart, front: Boolean): (AbsValue, Elem) + + /** remove */ + def remove(part: AbsPart, value: AbsValue): Elem + + /** copy objects */ + def copyObj(from: AbsPart)(to: AllocSite): Elem + + /** keys of map */ + def keys(part: AbsPart, intSorted: Boolean)(to: AllocSite): Elem + + /** has SubMap */ + def hasSubMap(tname: String): Boolean + + /** allocation of map with address partitions */ + def allocMap( + to: AllocSite, + tname: String, + pairs: Iterable[(AbsValue, AbsValue)] = Nil, + ): Elem + + /** allocation of list with address partitions */ + def allocList( + to: AllocSite, + values: Iterable[AbsValue] = Nil, + ): Elem + + /** allocation of symbol with address partitions */ + def allocSymbol(to: AllocSite, desc: AbsValue): Elem + + /** set type of objects */ + def setType(part: AbsPart, tname: String): Elem + + /** check contains */ + def contains(part: AbsPart, value: AbsValue): AbsValue + + /** conversion to string */ + def toString(detail: Boolean): String + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/heap/package.scala b/src/main/scala/esmeta/analyzer/domain/heap/package.scala new file mode 100644 index 0000000000..00b17ce817 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/heap/package.scala @@ -0,0 +1,7 @@ +package esmeta.analyzer.domain.heap + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl extends HeapDomainDecl with HeapBasicDomainDecl { self: Self => } diff --git a/src/main/scala/esmeta/analyzer/domain/math/Domain.scala b/src/main/scala/esmeta/analyzer/domain/math/Domain.scala deleted file mode 100644 index 3dc4a95984..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/math/Domain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.math - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** abstract mathematical value domain */ -trait Domain extends domain.Domain[Math] diff --git a/src/main/scala/esmeta/analyzer/domain/math/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/math/FlatDomain.scala deleted file mode 100644 index df8275ce3a..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/math/FlatDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.math - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for mathematical values */ -object FlatDomain extends math.Domain with FlatDomain[Math]("math") diff --git a/src/main/scala/esmeta/analyzer/domain/math/MathDomain.scala b/src/main/scala/esmeta/analyzer/domain/math/MathDomain.scala new file mode 100644 index 0000000000..fe695f1592 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/math/MathDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.math + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait MathDomainDecl { self: Self => + + /** abstract mathematical value domain */ + trait MathDomain extends Domain[Math] +} diff --git a/src/main/scala/esmeta/analyzer/domain/math/MathFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/math/MathFlatDomain.scala new file mode 100644 index 0000000000..434fadd99e --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/math/MathFlatDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.math + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait MathFlatDomainDecl { self: Self => + + /** flat domain for mathematical values */ + object MathFlatDomain extends MathDomain with FlatDomain[Math]("math") +} diff --git a/src/main/scala/esmeta/analyzer/domain/math/MathSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/math/MathSetDomain.scala new file mode 100644 index 0000000000..b5e0bf8b0a --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/math/MathSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.math + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait MathSetDomainDecl { self: Self => + + /** set domain for mathematical values */ + class MathSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends MathDomain + with SetDomain[Math]("math", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/math/MathSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/math/MathSimpleDomain.scala new file mode 100644 index 0000000000..80a5057bee --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/math/MathSimpleDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.math + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait MathSimpleDomainDecl { self: Self => + + /** simple domain for mathematical values */ + object MathSimpleDomain extends MathDomain with SimpleDomain[Math]("math") +} diff --git a/src/main/scala/esmeta/analyzer/domain/math/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/math/SetDomain.scala deleted file mode 100644 index e4f8bc1e47..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/math/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.math - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for mathematical values */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends math.Domain - with domain.SetDomain[Math]("math", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/math/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/math/SimpleDomain.scala deleted file mode 100644 index 13742d4a7e..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/math/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.math - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for mathematical values */ -object SimpleDomain extends math.Domain with SimpleDomain[Math]("math") diff --git a/src/main/scala/esmeta/analyzer/domain/math/package.scala b/src/main/scala/esmeta/analyzer/domain/math/package.scala new file mode 100644 index 0000000000..74aaa83bbf --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/math/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.math + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends MathDomainDecl + with MathSimpleDomainDecl + with MathFlatDomainDecl + with MathSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/nt/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/nt/FlatDomain.scala deleted file mode 100644 index 72dc797247..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/nt/FlatDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.nt - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for nonterminals */ -object FlatDomain extends nt.Domain with FlatDomain[Nt]("nt") diff --git a/src/main/scala/esmeta/analyzer/domain/nt/Domain.scala b/src/main/scala/esmeta/analyzer/domain/nt/NtDomain.scala similarity index 51% rename from src/main/scala/esmeta/analyzer/domain/nt/Domain.scala rename to src/main/scala/esmeta/analyzer/domain/nt/NtDomain.scala index 708acd13b3..4abb9e525f 100644 --- a/src/main/scala/esmeta/analyzer/domain/nt/Domain.scala +++ b/src/main/scala/esmeta/analyzer/domain/nt/NtDomain.scala @@ -4,5 +4,8 @@ import esmeta.analyzer.* import esmeta.analyzer.domain.* import esmeta.state.Nt -/** abstract nonterminal domain */ -trait Domain extends domain.Domain[Nt] +trait NtDomainDecl { self: Self => + + /** abstract nonterminal domain */ + trait NtDomain extends Domain[Nt] +} diff --git a/src/main/scala/esmeta/analyzer/domain/nt/NtFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/nt/NtFlatDomain.scala new file mode 100644 index 0000000000..e36755520d --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/nt/NtFlatDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.nt + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait NtFlatDomainDecl { self: Self => + + /** flat domain for nonterminals */ + object NtFlatDomain extends NtDomain with FlatDomain[Nt]("nt") +} diff --git a/src/main/scala/esmeta/analyzer/domain/nt/NtSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/nt/NtSetDomain.scala new file mode 100644 index 0000000000..51dcba1870 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/nt/NtSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.nt + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait NtSetDomainDecl { self: Self => + + /** set domain for nonterminals */ + class NtSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends NtDomain + with SetDomain[Nt]("nt", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/nt/NtSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/nt/NtSimpleDomain.scala new file mode 100644 index 0000000000..ad051aebf2 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/nt/NtSimpleDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.nt + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait NtSimpleDomainDecl { self: Self => + + /** simple domain for nonterminals */ + object NtSimpleDomain extends NtDomain with SimpleDomain[Nt]("nt") +} diff --git a/src/main/scala/esmeta/analyzer/domain/nt/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/nt/SetDomain.scala deleted file mode 100644 index 5b4394b4b1..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/nt/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.nt - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for nonterminals */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends nt.Domain - with domain.SetDomain[Nt]("nt", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/nt/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/nt/SimpleDomain.scala deleted file mode 100644 index c36e8d5ad4..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/nt/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.nt - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for nonterminals */ -object SimpleDomain extends nt.Domain with SimpleDomain[Nt]("nt") diff --git a/src/main/scala/esmeta/analyzer/domain/nt/package.scala b/src/main/scala/esmeta/analyzer/domain/nt/package.scala new file mode 100644 index 0000000000..c9106a1668 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/nt/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.nt + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends NtDomainDecl + with NtSimpleDomainDecl + with NtFlatDomainDecl + with NtSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/nullv/Domain.scala b/src/main/scala/esmeta/analyzer/domain/nullv/NullDomain.scala similarity index 51% rename from src/main/scala/esmeta/analyzer/domain/nullv/Domain.scala rename to src/main/scala/esmeta/analyzer/domain/nullv/NullDomain.scala index e1b1058006..ab7be8fbff 100644 --- a/src/main/scala/esmeta/analyzer/domain/nullv/Domain.scala +++ b/src/main/scala/esmeta/analyzer/domain/nullv/NullDomain.scala @@ -4,5 +4,8 @@ import esmeta.analyzer.* import esmeta.analyzer.domain.* import esmeta.state.* -/** abstract null domain */ -trait Domain extends domain.Domain[Null] +trait NullDomainDecl { self: Self => + + /** abstract null domain */ + trait NullDomain extends Domain[Null] +} diff --git a/src/main/scala/esmeta/analyzer/domain/nullv/NullSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/nullv/NullSimpleDomain.scala new file mode 100644 index 0000000000..331d18639a --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/nullv/NullSimpleDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.nullv + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.* + +trait NullSimpleDomainDecl { self: Self => + + /** simple domain for null values */ + object NullSimpleDomain + extends NullDomain + with SimpleDomain("null", Fin(Null)) +} diff --git a/src/main/scala/esmeta/analyzer/domain/nullv/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/nullv/SimpleDomain.scala deleted file mode 100644 index 88d308ca0b..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/nullv/SimpleDomain.scala +++ /dev/null @@ -1,9 +0,0 @@ -package esmeta.analyzer.domain.nullv - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.* - -/** simple domain for null values */ -object SimpleDomain extends nullv.Domain with SimpleDomain("null", Fin(Null)) diff --git a/src/main/scala/esmeta/analyzer/domain/nullv/package.scala b/src/main/scala/esmeta/analyzer/domain/nullv/package.scala new file mode 100644 index 0000000000..8a26a6c1e7 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/nullv/package.scala @@ -0,0 +1,7 @@ +package esmeta.analyzer.domain.nullv + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl extends NullDomainDecl with NullSimpleDomainDecl { self: Self => } diff --git a/src/main/scala/esmeta/analyzer/domain/number/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/number/FlatDomain.scala deleted file mode 100644 index c70abdfd63..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/number/FlatDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.number - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for number values */ -object FlatDomain extends number.Domain with FlatDomain[Number]("number") diff --git a/src/main/scala/esmeta/analyzer/domain/number/Domain.scala b/src/main/scala/esmeta/analyzer/domain/number/NumberDomain.scala similarity index 50% rename from src/main/scala/esmeta/analyzer/domain/number/Domain.scala rename to src/main/scala/esmeta/analyzer/domain/number/NumberDomain.scala index 29e774d9c7..4d682dcb38 100644 --- a/src/main/scala/esmeta/analyzer/domain/number/Domain.scala +++ b/src/main/scala/esmeta/analyzer/domain/number/NumberDomain.scala @@ -4,5 +4,8 @@ import esmeta.analyzer.* import esmeta.analyzer.domain.* import esmeta.state.* -/** abstract number domain */ -trait Domain extends domain.Domain[Number] +trait NumberDomainDecl { self: Self => + + /** abstract number domain */ + trait NumberDomain extends Domain[Number] +} diff --git a/src/main/scala/esmeta/analyzer/domain/number/NumberFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/number/NumberFlatDomain.scala new file mode 100644 index 0000000000..4b647dc274 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/number/NumberFlatDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.number + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait NumberFlatDomainDecl { self: Self => + + /** flat domain for number values */ + object NumberFlatDomain extends NumberDomain with FlatDomain[Number]("number") +} diff --git a/src/main/scala/esmeta/analyzer/domain/number/NumberSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/number/NumberSetDomain.scala new file mode 100644 index 0000000000..004905f7de --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/number/NumberSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.number + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait NumberSetDomainDecl { self: Self => + + /** set domain for number values */ + class NumberSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends NumberDomain + with SetDomain[Number]("number", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/number/NumberSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/number/NumberSimpleDomain.scala new file mode 100644 index 0000000000..ae902ee43d --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/number/NumberSimpleDomain.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.number + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait NumberSimpleDomainDecl { self: Self => + + /** simple domain for number values */ + object NumberSimpleDomain + extends NumberDomain + with SimpleDomain[Number]("number") +} diff --git a/src/main/scala/esmeta/analyzer/domain/number/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/number/SetDomain.scala deleted file mode 100644 index 8afe34579e..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/number/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.number - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for number values */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends number.Domain - with domain.SetDomain[Number]("number", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/number/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/number/SimpleDomain.scala deleted file mode 100644 index 2650217ef5..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/number/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.number - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for number values */ -object SimpleDomain extends number.Domain with SimpleDomain[Number]("number") diff --git a/src/main/scala/esmeta/analyzer/domain/number/package.scala b/src/main/scala/esmeta/analyzer/domain/number/package.scala new file mode 100644 index 0000000000..ffd4768b1c --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/number/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.number + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends NumberDomainDecl + with NumberSimpleDomainDecl + with NumberFlatDomainDecl + with NumberSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/obj/BasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/obj/BasicDomain.scala deleted file mode 100644 index 6ef8f1f7ad..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/obj/BasicDomain.scala +++ /dev/null @@ -1,461 +0,0 @@ -package esmeta.analyzer.domain.obj - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.parser.ESValueParser -import esmeta.state.* -import esmeta.util.* -import esmeta.util.Appender.* -import esmeta.util.BaseUtils.error - -/** basic domain for objects */ -object BasicDomain extends obj.Domain { - - /** elements */ - trait Elem extends Appendable - - /** top element */ - lazy val Top: Elem = exploded("top abstract object") - - /** bottom element */ - case object Bot extends Elem - - /** symbol elements */ - case class SymbolElem(desc: AbsValue) extends Elem - - /** map elements */ - sealed trait MapElem extends Elem: - /** map types */ - val tname: String - - /** merged properties */ - def mergedProp: AbsValue = this match - case MergedMap(_, prop, _) => prop - case PropMap(_, map, _) => AbsValue(map.keys) - - /** merged values */ - def mergedValue: AbsValue = this match - case MergedMap(_, _, value) => value - case PropMap(_, map, _) => map.values.foldLeft(AbsValue.Bot)(_ ⊔ _) - - /** merged map elements */ - case class MergedMap( - tname: String, - prop: AbsValue, - value: AbsValue, - ) extends MapElem - - /** property map elements with optional property orders */ - case class PropMap( - tname: String, - map: Map[AValue, AbsValue], - order: PropOrder, - ) extends MapElem - - /** property orders */ - type PropOrder = Option[Vector[AValue]] - extension (elem: PropOrder) { - def ⊑(that: PropOrder): Boolean = (elem, that) match - case (Some(l), Some(r)) if l == r => true - case _ => that == None - def ⊔(that: PropOrder): PropOrder = (elem, that) match - case (Some(l), Some(r)) if l == r => elem - case _ => None - } - - /** lists */ - sealed trait ListElem extends Elem: - /** merged value of all possible values */ - def mergedValue: AbsValue = this match - case MergedList(value) => value - case KeyWiseList(values) => values.foldLeft(AbsValue.Bot)(_ ⊔ _) - - /** merged lists */ - case class MergedList(value: AbsValue) extends ListElem - - /** key-wise lists */ - case class KeyWiseList(values: Vector[AbsValue]) extends ListElem - - /** not supported objects */ - case class NotSupportedElem(tname: String, desc: String) extends Elem - - /** get list with abstract values */ - def getList(values: Iterable[AbsValue]): Elem = KeyWiseList(values.toVector) - - /** get list with a merged abstract value */ - def getMergedList(value: AbsValue): Elem = MergedList(value) - - /** get symbol with abstract description value */ - def getSymbol(desc: AbsValue): Elem = SymbolElem(desc) - - /** abstraction functions */ - def alpha(obj: Obj): Elem = obj match - case SymbolObj(desc) => SymbolElem(AbsValue(desc)) - case m @ MapObj(tname, props, size) => - PropMap( - tname = tname, - map = (for { - (k, propV) <- props - } yield AValue.from(k) -> AbsValue(propV.value)).toMap, - order = Some(m.keys.map(AValue.from)), - ) - case ListObj(values) => KeyWiseList(values.map(AbsValue(_))) - case YetObj(tname, desc) => NotSupportedElem(tname, desc) - - /** abstraction functions for a single concrete object */ - def alpha(xs: Iterable[Obj]): Elem = xs.map(alpha).foldLeft[Elem](Bot)(_ ⊔ _) - - /** appender */ - given rule: Rule[Elem] = (app, elem) => - elem match - case Bot => app >> "⊥" - case SymbolElem(desc) => app >> "'" >> desc.toString - case MergedMap(tname, prop, value) => - app >> s"$tname " - app >> "{{" >> prop.toString >> " -> " >> value.toString >> "}}" - case PropMap(tname, map, order) => - app >> tname >> " " - if (!map.isEmpty) app.wrap { - order match - case Some(order) => - for { - (k, i) <- order.zipWithIndex - } app :> s"[$i] $k -> " >> map(k) - case None => - for { - (k, v) <- map - } app :> s"$k -> " >> v - } - else app >> "{}" - case MergedList(value) => - app >> "[[" >> value.toString >> "]]" - case KeyWiseList(values) => - app >> values.mkString("[", ", ", "]") - case NotSupportedElem(tname, desc) => - app >> s"???[$tname](" >> desc >> ")" - - /** object element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = (elem, that) match - case (Bot, _) => true - case (_, Bot) => false - case (SymbolElem(ldesc), SymbolElem(rdesc)) => ldesc ⊑ rdesc - case (PropMap(ltname, lmap, lorder), PropMap(rtname, rmap, rorder)) => - ltname == rtname && - lorder ⊑ rorder && - (lmap.keys ++ rmap.keys).forall(x => elem(x) ⊑ that(x)) - case (l: MapElem, r: MapElem) => - l.tname == r.tname && - l.mergedProp ⊑ r.mergedProp && - l.mergedValue ⊑ r.mergedValue - case (KeyWiseList(lvs), KeyWiseList(rvs)) => - lvs.length == rvs.length && - (lvs zip rvs).forall { case (l, r) => l ⊑ r } - case (l: ListElem, r: ListElem) => - l.mergedValue ⊑ r.mergedValue - case (NotSupportedElem(ltname, ld), NotSupportedElem(rtname, rd)) => - ltname == rtname && ld == rd - case _ => false - - /** join operator */ - def ⊔(that: Elem): Elem = (elem, that) match - case (Bot, _) => that - case (_, Bot) => elem - case _ if elem ⊑ that => that - case _ if that ⊑ elem => elem - case (SymbolElem(ldesc), SymbolElem(rdesc)) => SymbolElem(ldesc ⊔ rdesc) - case ( - PropMap(ltname, lmap, lorder), - PropMap(rtname, rmap, rorder), - ) if ltname == rtname => - PropMap( - tname = ltname, - map = (lmap.keys ++ rmap.keys).toList - .map(x => x -> (elem(x) ⊔ that(x))) - .toMap, - order = lorder ⊔ rorder, - ) - case (l: MapElem, r: MapElem) if l.tname == r.tname => - MergedMap( - tname = l.tname, - prop = l.mergedProp ⊔ r.mergedProp, - value = l.mergedValue ⊔ r.mergedValue, - ) - case (l @ KeyWiseList(lvs), r @ KeyWiseList(rvs)) => - if (lvs.length == rvs.length) { - KeyWiseList((lvs zip rvs).map { case (l, r) => l ⊔ r }) - } else MergedList(l.mergedValue ⊔ r.mergedValue) - case (l: ListElem, r: ListElem) => - MergedList(l.mergedValue ⊔ r.mergedValue) - case ( - NotSupportedElem(lty, ld), - NotSupportedElem(rty, rd), - ) if lty == rty && ld == rd => - elem - case _ => - exploded(s"cannot merge: ${elem.getTy} with ${that.getTy}") - - /** lookup */ - def apply(key: AValue): AbsValue = elem match - case Bot => AbsValue.Bot - case SymbolElem(desc) => - key match - case Str("Description") => desc - case _ => AbsValue.Bot - case MergedMap(_, prop, value) => - if (AbsValue(key) ⊑ prop) value - else AbsValue.absentTop - case m: PropMap => m.map.getOrElse(key, AbsValue.absentTop) - case MergedList(value) => value - case KeyWiseList(values) => - key match - case Math(math) => - val idx = math.toInt - if (0 <= idx && idx < values.length) values(idx) - else AbsValue.absentTop - case Str("length") => - AbsValue(Math(values.length)) - case _ => AbsValue.Bot - case NotSupportedElem(_, desc) => AbsValue.Bot - - /** lookup */ - def get(akey: AbsValue): AbsValue = akey.getSingle match - case Zero => AbsValue.Bot - case One(key) => elem(key) - case Many => - elem match - case Bot => AbsValue.Bot - case SymbolElem(desc) => desc - case m: MapElem => m.mergedValue - case l: ListElem => l.mergedValue - case NotSupportedElem(_, desc) => AbsValue.Bot - - /** get list with abstract values */ - def getList: Option[Vector[AbsValue]] = elem match - case KeyWiseList(vs) => Some(vs) - case _ => None - - /** get type */ - def getTy: String = elem match - case Bot => "" - case SymbolElem(desc) => "Symbol" - case m: MapElem => m.tname - case MergedList(value) => "List" - case KeyWiseList(values) => "List" - case NotSupportedElem(ty, desc) => ty - - /** singleton checks */ - def isSingle: Boolean = elem match - case SymbolElem(desc) => desc.isSingle - case PropMap(_, map, Some(_)) => map.forall { case (_, v) => v.isSingle } - case KeyWiseList(values) => values.forall(_.isSingle) - case NotSupportedElem(_, desc) => true - case _ => false - - /** get reachable address partitions */ - def reachableParts: Set[Part] = elem match - case SymbolElem(desc) => - desc.reachableParts - case MergedMap(_, prop, value) => - prop.reachableParts ++ value.reachableParts - case m: PropMap => - m.map.keySet.collect { case p: Part => p } - ++ m.map.values.flatMap(_.reachableParts).toSet - case MergedList(value) => - value.reachableParts - case KeyWiseList(values) => - values.foldLeft(Set[Part]())(_ ++ _.reachableParts) - case _ => - Set() - - /** updates */ - def update(prop: AbsValue, value: AbsValue, weak: Boolean): Elem = - def aux(key: AValue): MapUpdater = _ match { - case MergedMap(t, p, v) => MergedMap(t, p ⊔ prop, v ⊔ value) - case PropMap(ty, map, order) => - val newOrder = order match - case Some(order) if !map.contains(key) => Some(order :+ key) - case _ => order - PropMap(ty, map + (key -> value), newOrder) - } - def mergedAux: MapUpdater = m => - MergedMap( - m.tname, - m.mergedProp ⊔ prop, - m.mergedValue ⊔ value, - ) - modifyMap(elem, prop, aux, mergedAux, aux, mergedAux, weak) - - /** delete */ - def delete(prop: AbsValue, weak: Boolean): Elem = - def aux(key: AValue): MapUpdater = _ match { - case PropMap(ty, map, order) => - val newOrder = order match - case Some(order) if map contains key => Some(order.filter(_ != key)) - case _ => order - PropMap(ty, map - key, newOrder) - case m => m - } - def mergedAux: MapUpdater = m => - MergedMap( - m.tname, - m.mergedProp, - m.mergedValue, - ) - modifyMap(elem, prop, aux, mergedAux, aux, mergedAux, weak) - - /** concat */ - def concat(list: AbsObj, weak: Boolean): Elem = list match - case MergedList(value) => - modifyList(elem, x => x, _ ⊔ value, true) - case list @ KeyWiseList(values) => - modifyList(elem, _ ++ values, _ ⊔ list.mergedValue, weak) - case _ => Top - - /** duplicated element check */ - def duplicated: AbsBool = elem match - case _: MergedList => AB - case KeyWiseList(vs) if vs.forall(_.isSingle) => - val values = vs.map(_.getSingle).flatMap { - case One(v) => Some(v) - case _ => None - } - AbsBool(Bool(values.toSet.size != values.size)) - case _: KeyWiseList => AB - case _ => AbsBool.Bot - - /** appends */ - def append(value: AbsValue, weak: Boolean): Elem = - modifyList(elem, _ :+ value, _ ⊔ value, weak) - - /** prepends */ - def prepend(value: AbsValue, weak: Boolean): Elem = - modifyList(elem, value +: _, _ ⊔ value, weak) - - /** remove */ - def remove(value: AbsValue, weak: Boolean): Elem = - modifyList(elem, _.filter(v => v != value), _ ⊔ value, weak) - - /** pops */ - def pop(weak: Boolean, front: Boolean): (AbsValue, Elem) = elem match - case l: ListElem => - var v: AbsValue = AbsValue.Bot - val newObj = - modifyList( - elem, - vs => { - v = if (front) vs.head else vs.last - if (front) vs.drop(1) else vs.dropRight(1) - }, - mv => { v = mv; mv }, - weak, - ) - (v, newObj) - case _ => (AbsValue.Bot, Bot) - - /** keys of map */ - def keys(intSorted: Boolean): Elem = elem match - case MergedMap(_, prop, _) => MergedList(prop) - case PropMap(tname, map, Some(props)) => - KeyWiseList(if (intSorted) { - (for { - case Str(s) <- props - d = ESValueParser.str2Number(s) - if toStringHelper(d) == s - i = d.toLong - if d == i - } yield (s, i)) - .sortBy(_._2) - .map { case (s, _) => AbsValue(Str(s)) } - } else if (tname == "SubMap") { - props.map(AbsValue(_)) - } else props.sortBy(_.toString).map(AbsValue(_))) - case _ => Bot - - /** set type of objects */ - def setType(tname: String): Elem = elem match - case MergedMap(_, prop, value) => MergedMap(tname, prop, value) - case PropMap(_, map, props) => PropMap(tname, map, props) - case _ => error("cannot set type of non-map abstract objects.") - - /** check contains */ - def contains(value: AbsValue): AbsValue = (elem, value.getSingle) match - case (Bot, _) | (_, Zero) => AbsValue.Bot - case (KeyWiseList(values), One(_)) => - if (values contains value) AVT - else if (values.forall(v => (v ⊓ value).isBottom)) AVF - else AVB - case (MergedList(mergedValue), _) => - if ((mergedValue ⊓ value).isBottom) AVF - else AVB - case _ => AbsValue.Bot - - /** find merged parts */ - def findMerged( - part: Part, - path: String, - aux: (AbsValue, String, String) => Unit, - ): Unit = elem match - case Bot => - case SymbolElem(desc) => - aux(desc, s"$path.desc", s"$part.desc") - case PropMap(_, map, Some(_)) => - for ((p, v) <- map) { - aux(v, s"$path[$p]", s"$part[$p]") - } - case KeyWiseList(values) => - for ((v, k) <- values.zipWithIndex) { - aux(v, s"$path[$k]", s"$part[$k]") - } - case NotSupportedElem(_, _) => - case obj => println(s"$path ($part) is merged object: $obj") - } - - // --------------------------------------------------------------------------- - // private helpers - // --------------------------------------------------------------------------- - // helper for map structures - private type MapUpdater = MapElem => MapElem - private def modifyMap( - elem: Elem, - prop: AbsValue, - esF: AValue => MapUpdater, - esMergedF: MapUpdater, - f: AValue => MapUpdater, - mergedF: MapUpdater, - weak: Boolean, - ): Elem = elem match - // for ECMAScript - case map @ MergedMap("SubMap", _, _) => - esMergedF(map) - case map @ PropMap("SubMap", _, Some(_)) => - prop.keyValue.getSingle match - case Zero => elem - case One(key) if !weak => esF(key)(map) - case _ => esMergedF(map) - // for IR - case map @ MergedMap(ty, _, _) => - mergedF(map) - case map @ PropMap(ty, _, Some(_)) => - prop.keyValue.getSingle match - case Zero => elem - case One(key) if !weak => f(key)(map) - case _ => mergedF(map) - case _ => elem - - // helper for map structures - private type ListUpdater = Vector[AbsValue] => Vector[AbsValue] - private def modifyList( - elem: Elem, - f: ListUpdater, - mergedF: AbsValue => AbsValue, - weak: Boolean, - ): Elem = elem match - case l @ MergedList(value) => MergedList(mergedF(value)) - case l @ KeyWiseList(values) => - if (weak) MergedList(mergedF(l.mergedValue)) - else KeyWiseList(f(values)) - case _ => Bot -} diff --git a/src/main/scala/esmeta/analyzer/domain/obj/Domain.scala b/src/main/scala/esmeta/analyzer/domain/obj/Domain.scala deleted file mode 100644 index 16d66a2adc..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/obj/Domain.scala +++ /dev/null @@ -1,80 +0,0 @@ -package esmeta.analyzer.domain.obj - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** abstract object domain */ -trait Domain extends domain.Domain[Obj] { - - /** get list with abstract values */ - def getList(values: Iterable[AbsValue] = Nil): Elem - - /** get list with a merged abstract value */ - def getMergedList(value: AbsValue): Elem - - /** get symbol with abstract description value */ - def getSymbol(desc: AbsValue): Elem - - /** object element interfaces */ - extension (elem: Elem) { - - /** lookup */ - def apply(key: AValue): AbsValue - - /** lookup */ - def get(akey: AbsValue): AbsValue - - /** get list with abstract values */ - def getList: Option[Vector[AbsValue]] - - /** get type */ - def getTy: String - - /** singleton checks */ - def isSingle: Boolean - - /** get reachable address partitions */ - def reachableParts: Set[Part] - - /** updates */ - def update(prop: AbsValue, value: AbsValue, weak: Boolean): Elem - - /** delete */ - def delete(prop: AbsValue, weak: Boolean): Elem - - /** concat */ - def concat(list: AbsObj, weak: Boolean): Elem - - /** duplicated element check */ - def duplicated: AbsBool - - /** append */ - def append(value: AbsValue, weak: Boolean): Elem - - /** prepend */ - def prepend(value: AbsValue, weak: Boolean): Elem - - /** remove */ - def remove(value: AbsValue, weak: Boolean): Elem - - /** pops */ - def pop(weak: Boolean, front: Boolean): (AbsValue, Elem) - - /** keys of map */ - def keys(intSorted: Boolean): Elem - - /** set type of objects */ - def setType(tname: String): Elem - - /** check contains */ - def contains(value: AbsValue): AbsValue - - /** find merged parts */ - def findMerged( - part: Part, - path: String, - aux: (AbsValue, String, String) => Unit, - ): Unit - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/obj/ObjBasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/obj/ObjBasicDomain.scala new file mode 100644 index 0000000000..eff052306e --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/obj/ObjBasicDomain.scala @@ -0,0 +1,467 @@ +package esmeta.analyzer.domain.obj + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.parser.ESValueParser +import esmeta.state.* +import esmeta.util.* +import esmeta.util.Appender.* +import esmeta.util.BaseUtils.error + +trait ObjBasicDomainDecl { self: Self => + + /** basic domain for objects */ + object ObjBasicDomain extends ObjDomain { + + /** elements */ + trait Elem extends Appendable + + /** top element */ + lazy val Top: Elem = exploded("top abstract object") + + /** bottom element */ + case object Bot extends Elem + + /** symbol elements */ + case class SymbolElem(desc: AbsValue) extends Elem + + /** map elements */ + sealed trait MapElem extends Elem: + /** map types */ + val tname: String + + /** merged properties */ + def mergedProp: AbsValue = this match + case MergedMap(_, prop, _) => prop + case PropMap(_, map, _) => AbsValue(map.keys) + + /** merged values */ + def mergedValue: AbsValue = this match + case MergedMap(_, _, value) => value + case PropMap(_, map, _) => map.values.foldLeft(AbsValue.Bot)(_ ⊔ _) + + /** merged map elements */ + case class MergedMap( + tname: String, + prop: AbsValue, + value: AbsValue, + ) extends MapElem + + /** property map elements with optional property orders */ + case class PropMap( + tname: String, + map: Map[AValue, AbsValue], + order: PropOrder, + ) extends MapElem + + /** property orders */ + type PropOrder = Option[Vector[AValue]] + extension (elem: PropOrder) { + def ⊑(that: PropOrder): Boolean = (elem, that) match + case (Some(l), Some(r)) if l == r => true + case _ => that == None + def ⊔(that: PropOrder): PropOrder = (elem, that) match + case (Some(l), Some(r)) if l == r => elem + case _ => None + } + + /** lists */ + sealed trait ListElem extends Elem: + /** merged value of all possible values */ + def mergedValue: AbsValue = this match + case MergedList(value) => value + case KeyWiseList(values) => values.foldLeft(AbsValue.Bot)(_ ⊔ _) + + /** merged lists */ + case class MergedList(value: AbsValue) extends ListElem + + /** key-wise lists */ + case class KeyWiseList(values: Vector[AbsValue]) extends ListElem + + /** not supported objects */ + case class NotSupportedElem(tname: String, desc: String) extends Elem + + /** get list with abstract values */ + def getList(values: Iterable[AbsValue]): Elem = KeyWiseList(values.toVector) + + /** get list with a merged abstract value */ + def getMergedList(value: AbsValue): Elem = MergedList(value) + + /** get symbol with abstract description value */ + def getSymbol(desc: AbsValue): Elem = SymbolElem(desc) + + /** abstraction functions */ + def alpha(obj: Obj): Elem = obj match + case SymbolObj(desc) => SymbolElem(AbsValue(desc)) + case m @ MapObj(tname, props, size) => + PropMap( + tname = tname, + map = (for { + (k, propV) <- props + } yield AValue.from(k) -> AbsValue(propV.value)).toMap, + order = Some(m.keys.map(AValue.from)), + ) + case ListObj(values) => KeyWiseList(values.map(AbsValue(_))) + case YetObj(tname, desc) => NotSupportedElem(tname, desc) + + /** abstraction functions for a single concrete object */ + def alpha(xs: Iterable[Obj]): Elem = + xs.map(alpha).foldLeft[Elem](Bot)(_ ⊔ _) + + /** appender */ + given rule: Rule[Elem] = (app, elem) => + elem match + case Bot => app >> "⊥" + case SymbolElem(desc) => app >> "'" >> desc.toString + case MergedMap(tname, prop, value) => + app >> s"$tname " + app >> "{{" >> prop.toString >> " -> " >> value.toString >> "}}" + case PropMap(tname, map, order) => + app >> tname >> " " + if (!map.isEmpty) app.wrap { + order match + case Some(order) => + for { + (k, i) <- order.zipWithIndex + } app :> s"[$i] $k -> " >> map(k) + case None => + for { + (k, v) <- map + } app :> s"$k -> " >> v + } + else app >> "{}" + case MergedList(value) => + app >> "[[" >> value.toString >> "]]" + case KeyWiseList(values) => + app >> values.mkString("[", ", ", "]") + case NotSupportedElem(tname, desc) => + app >> s"???[$tname](" >> desc >> ")" + + /** object element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = (elem, that) match + case (Bot, _) => true + case (_, Bot) => false + case (SymbolElem(ldesc), SymbolElem(rdesc)) => ldesc ⊑ rdesc + case (PropMap(ltname, lmap, lorder), PropMap(rtname, rmap, rorder)) => + ltname == rtname && + lorder ⊑ rorder && + (lmap.keys ++ rmap.keys).forall(x => elem(x) ⊑ that(x)) + case (l: MapElem, r: MapElem) => + l.tname == r.tname && + l.mergedProp ⊑ r.mergedProp && + l.mergedValue ⊑ r.mergedValue + case (KeyWiseList(lvs), KeyWiseList(rvs)) => + lvs.length == rvs.length && + (lvs zip rvs).forall { case (l, r) => l ⊑ r } + case (l: ListElem, r: ListElem) => + l.mergedValue ⊑ r.mergedValue + case (NotSupportedElem(ltname, ld), NotSupportedElem(rtname, rd)) => + ltname == rtname && ld == rd + case _ => false + + /** join operator */ + def ⊔(that: Elem): Elem = (elem, that) match + case (Bot, _) => that + case (_, Bot) => elem + case _ if elem ⊑ that => that + case _ if that ⊑ elem => elem + case (SymbolElem(ldesc), SymbolElem(rdesc)) => SymbolElem(ldesc ⊔ rdesc) + case ( + PropMap(ltname, lmap, lorder), + PropMap(rtname, rmap, rorder), + ) if ltname == rtname => + PropMap( + tname = ltname, + map = (lmap.keys ++ rmap.keys).toList + .map(x => x -> (elem(x) ⊔ that(x))) + .toMap, + order = lorder ⊔ rorder, + ) + case (l: MapElem, r: MapElem) if l.tname == r.tname => + MergedMap( + tname = l.tname, + prop = l.mergedProp ⊔ r.mergedProp, + value = l.mergedValue ⊔ r.mergedValue, + ) + case (l @ KeyWiseList(lvs), r @ KeyWiseList(rvs)) => + if (lvs.length == rvs.length) { + KeyWiseList((lvs zip rvs).map { case (l, r) => l ⊔ r }) + } else MergedList(l.mergedValue ⊔ r.mergedValue) + case (l: ListElem, r: ListElem) => + MergedList(l.mergedValue ⊔ r.mergedValue) + case ( + NotSupportedElem(lty, ld), + NotSupportedElem(rty, rd), + ) if lty == rty && ld == rd => + elem + case _ => + exploded(s"cannot merge: ${elem.getTy} with ${that.getTy}") + + /** lookup */ + def apply(key: AValue): AbsValue = elem match + case Bot => AbsValue.Bot + case SymbolElem(desc) => + key match + case Str("Description") => desc + case _ => AbsValue.Bot + case MergedMap(_, prop, value) => + if (AbsValue(key) ⊑ prop) value + else AbsValue.absentTop + case m: PropMap => m.map.getOrElse(key, AbsValue.absentTop) + case MergedList(value) => value + case KeyWiseList(values) => + key match + case Math(math) => + val idx = math.toInt + if (0 <= idx && idx < values.length) values(idx) + else AbsValue.absentTop + case Str("length") => + AbsValue(Math(values.length)) + case _ => AbsValue.Bot + case NotSupportedElem(_, desc) => AbsValue.Bot + + /** lookup */ + def get(akey: AbsValue): AbsValue = akey.getSingle match + case Zero => AbsValue.Bot + case One(key) => elem(key) + case Many => + elem match + case Bot => AbsValue.Bot + case SymbolElem(desc) => desc + case m: MapElem => m.mergedValue + case l: ListElem => l.mergedValue + case NotSupportedElem(_, desc) => AbsValue.Bot + + /** get list with abstract values */ + def getList: Option[Vector[AbsValue]] = elem match + case KeyWiseList(vs) => Some(vs) + case _ => None + + /** get type */ + def getTy: String = elem match + case Bot => "" + case SymbolElem(desc) => "Symbol" + case m: MapElem => m.tname + case MergedList(value) => "List" + case KeyWiseList(values) => "List" + case NotSupportedElem(ty, desc) => ty + + /** singleton checks */ + def isSingle: Boolean = elem match + case SymbolElem(desc) => desc.isSingle + case PropMap(_, map, Some(_)) => + map.forall { case (_, v) => v.isSingle } + case KeyWiseList(values) => values.forall(_.isSingle) + case NotSupportedElem(_, desc) => true + case _ => false + + /** get reachable address partitions */ + def reachableParts: Set[Part] = elem match + case SymbolElem(desc) => + desc.reachableParts + case MergedMap(_, prop, value) => + prop.reachableParts ++ value.reachableParts + case m: PropMap => + m.map.keySet.collect { case p: Part => p } + ++ m.map.values.flatMap(_.reachableParts).toSet + case MergedList(value) => + value.reachableParts + case KeyWiseList(values) => + values.foldLeft(Set[Part]())(_ ++ _.reachableParts) + case _ => + Set() + + /** updates */ + def update(prop: AbsValue, value: AbsValue, weak: Boolean): Elem = + def aux(key: AValue): MapUpdater = _ match { + case MergedMap(t, p, v) => MergedMap(t, p ⊔ prop, v ⊔ value) + case PropMap(ty, map, order) => + val newOrder = order match + case Some(order) if !map.contains(key) => Some(order :+ key) + case _ => order + PropMap(ty, map + (key -> value), newOrder) + } + def mergedAux: MapUpdater = m => + MergedMap( + m.tname, + m.mergedProp ⊔ prop, + m.mergedValue ⊔ value, + ) + modifyMap(elem, prop, aux, mergedAux, aux, mergedAux, weak) + + /** delete */ + def delete(prop: AbsValue, weak: Boolean): Elem = + def aux(key: AValue): MapUpdater = _ match { + case PropMap(ty, map, order) => + val newOrder = order match + case Some(order) if map contains key => + Some(order.filter(_ != key)) + case _ => order + PropMap(ty, map - key, newOrder) + case m => m + } + def mergedAux: MapUpdater = m => + MergedMap( + m.tname, + m.mergedProp, + m.mergedValue, + ) + modifyMap(elem, prop, aux, mergedAux, aux, mergedAux, weak) + + /** concat */ + def concat(list: AbsObj, weak: Boolean): Elem = list match + case MergedList(value) => + modifyList(elem, x => x, _ ⊔ value, true) + case list @ KeyWiseList(values) => + modifyList(elem, _ ++ values, _ ⊔ list.mergedValue, weak) + case _ => Top + + /** duplicated element check */ + def duplicated: AbsBool = elem match + case _: MergedList => AB + case KeyWiseList(vs) if vs.forall(_.isSingle) => + val values = vs.map(_.getSingle).flatMap { + case One(v) => Some(v) + case _ => None + } + AbsBool(Bool(values.toSet.size != values.size)) + case _: KeyWiseList => AB + case _ => AbsBool.Bot + + /** appends */ + def append(value: AbsValue, weak: Boolean): Elem = + modifyList(elem, _ :+ value, _ ⊔ value, weak) + + /** prepends */ + def prepend(value: AbsValue, weak: Boolean): Elem = + modifyList(elem, value +: _, _ ⊔ value, weak) + + /** remove */ + def remove(value: AbsValue, weak: Boolean): Elem = + modifyList(elem, _.filter(v => v != value), _ ⊔ value, weak) + + /** pops */ + def pop(weak: Boolean, front: Boolean): (AbsValue, Elem) = elem match + case l: ListElem => + var v: AbsValue = AbsValue.Bot + val newObj = + modifyList( + elem, + vs => { + v = if (front) vs.head else vs.last + if (front) vs.drop(1) else vs.dropRight(1) + }, + mv => { v = mv; mv }, + weak, + ) + (v, newObj) + case _ => (AbsValue.Bot, Bot) + + /** keys of map */ + def keys(intSorted: Boolean): Elem = elem match + case MergedMap(_, prop, _) => MergedList(prop) + case PropMap(tname, map, Some(props)) => + KeyWiseList(if (intSorted) { + (for { + case Str(s) <- props + d = ESValueParser.str2Number(s) + if toStringHelper(d) == s + i = d.toLong + if d == i + } yield (s, i)) + .sortBy(_._2) + .map { case (s, _) => AbsValue(Str(s)) } + } else if (tname == "SubMap") { + props.map(AbsValue(_)) + } else props.sortBy(_.toString).map(AbsValue(_))) + case _ => Bot + + /** set type of objects */ + def setType(tname: String): Elem = elem match + case MergedMap(_, prop, value) => MergedMap(tname, prop, value) + case PropMap(_, map, props) => PropMap(tname, map, props) + case _ => error("cannot set type of non-map abstract objects.") + + /** check contains */ + def contains(value: AbsValue): AbsValue = (elem, value.getSingle) match + case (Bot, _) | (_, Zero) => AbsValue.Bot + case (KeyWiseList(values), One(_)) => + if (values contains value) AVT + else if (values.forall(v => (v ⊓ value).isBottom)) AVF + else AVB + case (MergedList(mergedValue), _) => + if ((mergedValue ⊓ value).isBottom) AVF + else AVB + case _ => AbsValue.Bot + + /** find merged parts */ + def findMerged( + part: Part, + path: String, + aux: (AbsValue, String, String) => Unit, + ): Unit = elem match + case Bot => + case SymbolElem(desc) => + aux(desc, s"$path.desc", s"$part.desc") + case PropMap(_, map, Some(_)) => + for ((p, v) <- map) { + aux(v, s"$path[$p]", s"$part[$p]") + } + case KeyWiseList(values) => + for ((v, k) <- values.zipWithIndex) { + aux(v, s"$path[$k]", s"$part[$k]") + } + case NotSupportedElem(_, _) => + case obj => println(s"$path ($part) is merged object: $obj") + } + + // ------------------------------------------------------------------------- + // private helpers + // ------------------------------------------------------------------------- + // helper for map structures + private type MapUpdater = MapElem => MapElem + private def modifyMap( + elem: Elem, + prop: AbsValue, + esF: AValue => MapUpdater, + esMergedF: MapUpdater, + f: AValue => MapUpdater, + mergedF: MapUpdater, + weak: Boolean, + ): Elem = elem match + // for ECMAScript + case map @ MergedMap("SubMap", _, _) => + esMergedF(map) + case map @ PropMap("SubMap", _, Some(_)) => + prop.keyValue.getSingle match + case Zero => elem + case One(key) if !weak => esF(key)(map) + case _ => esMergedF(map) + // for IR + case map @ MergedMap(ty, _, _) => + mergedF(map) + case map @ PropMap(ty, _, Some(_)) => + prop.keyValue.getSingle match + case Zero => elem + case One(key) if !weak => f(key)(map) + case _ => mergedF(map) + case _ => elem + + // helper for map structures + private type ListUpdater = Vector[AbsValue] => Vector[AbsValue] + private def modifyList( + elem: Elem, + f: ListUpdater, + mergedF: AbsValue => AbsValue, + weak: Boolean, + ): Elem = elem match + case l @ MergedList(value) => MergedList(mergedF(value)) + case l @ KeyWiseList(values) => + if (weak) MergedList(mergedF(l.mergedValue)) + else KeyWiseList(f(values)) + case _ => Bot + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/obj/ObjDomain.scala b/src/main/scala/esmeta/analyzer/domain/obj/ObjDomain.scala new file mode 100644 index 0000000000..6254a73245 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/obj/ObjDomain.scala @@ -0,0 +1,83 @@ +package esmeta.analyzer.domain.obj + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait ObjDomainDecl { self: Self => + + /** abstract object domain */ + trait ObjDomain extends Domain[Obj] { + + /** get list with abstract values */ + def getList(values: Iterable[AbsValue] = Nil): Elem + + /** get list with a merged abstract value */ + def getMergedList(value: AbsValue): Elem + + /** get symbol with abstract description value */ + def getSymbol(desc: AbsValue): Elem + + /** object element interfaces */ + extension (elem: Elem) { + + /** lookup */ + def apply(key: AValue): AbsValue + + /** lookup */ + def get(akey: AbsValue): AbsValue + + /** get list with abstract values */ + def getList: Option[Vector[AbsValue]] + + /** get type */ + def getTy: String + + /** singleton checks */ + def isSingle: Boolean + + /** get reachable address partitions */ + def reachableParts: Set[Part] + + /** updates */ + def update(prop: AbsValue, value: AbsValue, weak: Boolean): Elem + + /** delete */ + def delete(prop: AbsValue, weak: Boolean): Elem + + /** concat */ + def concat(list: AbsObj, weak: Boolean): Elem + + /** duplicated element check */ + def duplicated: AbsBool + + /** append */ + def append(value: AbsValue, weak: Boolean): Elem + + /** prepend */ + def prepend(value: AbsValue, weak: Boolean): Elem + + /** remove */ + def remove(value: AbsValue, weak: Boolean): Elem + + /** pops */ + def pop(weak: Boolean, front: Boolean): (AbsValue, Elem) + + /** keys of map */ + def keys(intSorted: Boolean): Elem + + /** set type of objects */ + def setType(tname: String): Elem + + /** check contains */ + def contains(value: AbsValue): AbsValue + + /** find merged parts */ + def findMerged( + part: Part, + path: String, + aux: (AbsValue, String, String) => Unit, + ): Unit + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/obj/package.scala b/src/main/scala/esmeta/analyzer/domain/obj/package.scala new file mode 100644 index 0000000000..5689f69f90 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/obj/package.scala @@ -0,0 +1,7 @@ +package esmeta.analyzer.domain.obj + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl extends ObjDomainDecl with ObjBasicDomainDecl { self: Self => } diff --git a/src/main/scala/esmeta/analyzer/domain/package.scala b/src/main/scala/esmeta/analyzer/domain/package.scala new file mode 100644 index 0000000000..90a711274b --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/package.scala @@ -0,0 +1,39 @@ +package esmeta.analyzer.domain + +import esmeta.analyzer.Analyzer + +type Self = Decl & Analyzer + +trait Decl + extends AValueDecl + with AbsRefValueDecl + with DomainDecl + with FlatDomainDecl + with OptionDomainDecl + with SetDomainDecl + with SimpleDomainDecl + with absent.Decl + with astValue.Decl + with bigInt.Decl + with bool.Decl + with clo.Decl + with codeUnit.Decl + with comp.Decl + with const.Decl + with cont.Decl + with heap.Decl + with math.Decl + with nt.Decl + with nullv.Decl + with number.Decl + with obj.Decl + with part.Decl + with pureValue.Decl + with ret.Decl + with simpleValue.Decl + with state.Decl + with str.Decl + with undef.Decl + with value.Decl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/part/Domain.scala b/src/main/scala/esmeta/analyzer/domain/part/Domain.scala deleted file mode 100644 index b43560ef80..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/part/Domain.scala +++ /dev/null @@ -1,12 +0,0 @@ -package esmeta.analyzer.domain.part - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.Addr - -/** abstract address partition domain */ -trait Domain extends domain.Domain[Part] { - - /** abstraction functions for an original address */ - def alpha(addr: Addr): Elem = alpha(Part.from(addr)) -} diff --git a/src/main/scala/esmeta/analyzer/domain/part/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/part/FlatDomain.scala deleted file mode 100644 index cfd1b5b6f4..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/part/FlatDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.part - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** flat domain for address partitions */ -object FlatDomain extends part.Domain with FlatDomain[Part]("part") diff --git a/src/main/scala/esmeta/analyzer/domain/part/PartDomain.scala b/src/main/scala/esmeta/analyzer/domain/part/PartDomain.scala new file mode 100644 index 0000000000..48c00d4952 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/part/PartDomain.scala @@ -0,0 +1,15 @@ +package esmeta.analyzer.domain.part + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.Addr + +trait PartDomainDecl { self: Self => + + /** abstract address partition domain */ + trait PartDomain extends Domain[Part] { + + /** abstraction functions for an original address */ + def alpha(addr: Addr): Elem = alpha(Part.from(addr)) + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/part/PartFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/part/PartFlatDomain.scala new file mode 100644 index 0000000000..ef5d840f67 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/part/PartFlatDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.part + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait PartFlatDomainDecl { self: Self => + + /** flat domain for address partitions */ + object PartFlatDomain extends PartDomain with FlatDomain[Part]("part") +} diff --git a/src/main/scala/esmeta/analyzer/domain/part/PartSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/part/PartSetDomain.scala new file mode 100644 index 0000000000..607f66f7f1 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/part/PartSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.part + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait PartSetDomainDecl { self: Self => + + /** set domain for address partitions */ + class PartSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends PartDomain + with SetDomain[Part]("part", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/part/PartSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/part/PartSimpleDomain.scala new file mode 100644 index 0000000000..4accc57681 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/part/PartSimpleDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.part + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait PartSimpleDomainDecl { self: Self => + + /** simple domain for address partitions */ + object PartSimpleDomain extends PartDomain with SimpleDomain[Part]("part") +} diff --git a/src/main/scala/esmeta/analyzer/domain/part/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/part/SetDomain.scala deleted file mode 100644 index 222ff43780..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/part/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.part - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for address partitions */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends part.Domain - with domain.SetDomain[Part]("part", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/part/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/part/SimpleDomain.scala deleted file mode 100644 index bd9e86faa3..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/part/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.part - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for address partitions */ -object SimpleDomain extends part.Domain with SimpleDomain[Part]("part") diff --git a/src/main/scala/esmeta/analyzer/domain/part/package.scala b/src/main/scala/esmeta/analyzer/domain/part/package.scala new file mode 100644 index 0000000000..949ca3573d --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/part/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.part + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends PartDomainDecl + with PartSimpleDomainDecl + with PartFlatDomainDecl + with PartSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/pureValue/BasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/pureValue/BasicDomain.scala deleted file mode 100644 index 8c543e6688..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/pureValue/BasicDomain.scala +++ /dev/null @@ -1,234 +0,0 @@ -package esmeta.analyzer.domain.pureValue - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.* -import esmeta.util.Appender.* - -/** basic domain for pure values */ -object BasicDomain extends pureValue.Domain { - - /** elements */ - case class Elem( - clo: AbsClo = AbsClo.Bot, - cont: AbsCont = AbsCont.Bot, - part: AbsPart = AbsPart.Bot, - astValue: AbsAstValue = AbsAstValue.Bot, - nt: AbsNt = AbsNt.Bot, - codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, - const: AbsConst = AbsConst.Bot, - math: AbsMath = AbsMath.Bot, - simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, - ) extends Appendable - - /** top element */ - lazy val Top: Elem = exploded("top abstract pure value") - - /** bottom element */ - val Bot: Elem = Elem() - - /** abstraction functions */ - def alpha(xs: Iterable[APureValue]): Elem = Elem( - AbsClo(xs.collect { case x: AClo => x }), - AbsCont(xs.collect { case x: ACont => x }), - AbsPart(xs.collect { case x: Part => x }), - AbsAstValue(xs.collect { case x: AstValue => x }), - AbsNt(xs.collect { case x: Nt => x }), - AbsCodeUnit(xs.collect { case x: CodeUnit => x }), - AbsConst(xs.collect { case x: Const => x }), - AbsMath(xs.collect { case x: Math => x }), - AbsSimpleValue(xs.collect { case x: SimpleValue => x }), - ) - - /** predefined top values */ - val cloTop: Elem = Bot.copy(clo = AbsClo.Top) - val contTop: Elem = Bot.copy(cont = AbsCont.Top) - val partTop: Elem = Bot.copy(part = AbsPart.Top) - val astValueTop: Elem = Bot.copy(astValue = AbsAstValue.Top) - val ntTop: Elem = Bot.copy(nt = AbsNt.Top) - val codeUnitTop: Elem = Bot.copy(codeUnit = AbsCodeUnit.Top) - val constTop: Elem = Bot.copy(const = AbsConst.Top) - val mathTop: Elem = Bot.copy(math = AbsMath.Top) - val simpleValueTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.Top) - val numberTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.numberTop) - val bigIntTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.bigIntTop) - val strTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.strTop) - val boolTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.boolTop) - val undefTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.undefTop) - val nullTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.nullTop) - val absentTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.absentTop) - - /** constructors */ - def apply( - clo: AbsClo = AbsClo.Bot, - cont: AbsCont = AbsCont.Bot, - part: AbsPart = AbsPart.Bot, - astValue: AbsAstValue = AbsAstValue.Bot, - nt: AbsNt = AbsNt.Bot, - codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, - const: AbsConst = AbsConst.Bot, - math: AbsMath = AbsMath.Bot, - simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, - num: AbsNumber = AbsNumber.Bot, - bigInt: AbsBigInt = AbsBigInt.Bot, - str: AbsStr = AbsStr.Bot, - bool: AbsBool = AbsBool.Bot, - undef: AbsUndef = AbsUndef.Bot, - nullv: AbsNull = AbsNull.Bot, - absent: AbsAbsent = AbsAbsent.Bot, - ): Elem = Elem( - clo, - cont, - part, - astValue, - nt, - codeUnit, - const, - math, - simpleValue ⊔ AbsSimpleValue(num, bigInt, str, bool, undef, nullv, absent), - ) - - /** extractors */ - def unapply(elem: Elem): Option[RawTuple] = Some( - ( - elem.clo, - elem.cont, - elem.part, - elem.astValue, - elem.nt, - elem.codeUnit, - elem.const, - elem.math, - elem.simpleValue, - ), - ) - - /** appender */ - given rule: Rule[Elem] = (app, elem) => { - if (elem.isBottom) app >> "⊥" - else { - var strs = Vector[String]() - if (!elem.clo.isBottom) strs :+= elem.clo.toString - if (!elem.cont.isBottom) strs :+= elem.cont.toString - if (!elem.part.isBottom) strs :+= elem.part.toString - if (!elem.astValue.isBottom) strs :+= elem.astValue.toString - if (!elem.nt.isBottom) strs :+= elem.nt.toString - if (!elem.codeUnit.isBottom) strs :+= elem.codeUnit.toString - if (!elem.const.isBottom) strs :+= elem.const.toString - if (!elem.math.isBottom) strs :+= elem.math.toString - if (!elem.simpleValue.isBottom) strs :+= elem.simpleValue.toString - app >> strs.mkString(", ") - } - } - - /** element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = - elem.clo ⊑ that.clo && - elem.cont ⊑ that.cont && - elem.part ⊑ that.part && - elem.astValue ⊑ that.astValue && - elem.nt ⊑ that.nt && - elem.codeUnit ⊑ that.codeUnit && - elem.const ⊑ that.const && - elem.math ⊑ that.math && - elem.simpleValue ⊑ that.simpleValue - - /** join operator */ - def ⊔(that: Elem): Elem = Elem( - elem.clo ⊔ that.clo, - elem.cont ⊔ that.cont, - elem.part ⊔ that.part, - elem.astValue ⊔ that.astValue, - elem.nt ⊔ that.nt, - elem.codeUnit ⊔ that.codeUnit, - elem.const ⊔ that.const, - elem.math ⊔ that.math, - elem.simpleValue ⊔ that.simpleValue, - ) - - /** meet operator */ - override def ⊓(that: Elem): Elem = Elem( - elem.clo ⊓ that.clo, - elem.cont ⊓ that.cont, - elem.part ⊓ that.part, - elem.astValue ⊓ that.astValue, - elem.nt ⊓ that.nt, - elem.codeUnit ⊓ that.codeUnit, - elem.const ⊓ that.const, - elem.math ⊓ that.math, - elem.simpleValue ⊓ that.simpleValue, - ) - - /** prune operator */ - override def --(that: Elem): Elem = Elem( - elem.clo -- that.clo, - elem.cont -- that.cont, - elem.part -- that.part, - elem.astValue -- that.astValue, - elem.nt -- that.nt, - elem.codeUnit -- that.codeUnit, - elem.const -- that.const, - elem.math -- that.math, - elem.simpleValue -- that.simpleValue, - ) - - /** concretization function */ - override def gamma: BSet[APureValue] = - (elem.clo.gamma: BSet[APureValue]) ⊔ - elem.cont.gamma ⊔ - elem.part.gamma ⊔ - elem.astValue.gamma ⊔ - elem.nt.gamma ⊔ - elem.codeUnit.gamma ⊔ - elem.const.gamma ⊔ - elem.math.gamma ⊔ - elem.simpleValue.gamma - - /** get single value */ - override def getSingle: Flat[APureValue] = - (elem.clo.getSingle: Flat[APureValue]) ⊔ - elem.cont.getSingle ⊔ - elem.part.getSingle ⊔ - elem.astValue.getSingle ⊔ - elem.nt.getSingle ⊔ - elem.codeUnit.getSingle ⊔ - elem.const.getSingle ⊔ - elem.math.getSingle ⊔ - elem.simpleValue.getSingle - - /** getters */ - def clo: AbsClo = elem.clo - def cont: AbsCont = elem.cont - def part: AbsPart = elem.part - def astValue: AbsAstValue = elem.astValue - def nt: AbsNt = elem.nt - def codeUnit: AbsCodeUnit = elem.codeUnit - def const: AbsConst = elem.const - def math: AbsMath = elem.math - def simpleValue: AbsSimpleValue = elem.simpleValue - def number: AbsNumber = elem.simpleValue.number - def bigInt: AbsBigInt = elem.simpleValue.bigInt - def str: AbsStr = elem.simpleValue.str - def bool: AbsBool = elem.simpleValue.bool - def undef: AbsUndef = elem.simpleValue.undef - def nullv: AbsNull = elem.simpleValue.nullv - def absent: AbsAbsent = elem.simpleValue.absent - - /** get reachable address partitions */ - def reachableParts: Set[Part] = - var parts = part.toSet - for { - AClo(_, captured) <- clo - (_, value) <- captured - } parts ++= value.reachableParts - for { - ACont(_, captured) <- cont - (_, value) <- captured - } parts ++= value.reachableParts - parts - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/pureValue/Domain.scala b/src/main/scala/esmeta/analyzer/domain/pureValue/Domain.scala deleted file mode 100644 index 830855bdde..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/pureValue/Domain.scala +++ /dev/null @@ -1,94 +0,0 @@ -package esmeta.analyzer.domain.pureValue - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** abstract pure value (value except completion record) domain */ -trait Domain extends domain.Domain[APureValue] { - - /** abstraction functions for an original pure value */ - def alpha(pureValue: PureValue): Elem = alpha(APureValue.from(pureValue)) - - /** predefined top values */ - def cloTop: Elem - def contTop: Elem - def partTop: Elem - def astValueTop: Elem - def ntTop: Elem - def codeUnitTop: Elem - def constTop: Elem - def mathTop: Elem - def simpleValueTop: Elem - def numberTop: Elem - def bigIntTop: Elem - def strTop: Elem - def boolTop: Elem - def undefTop: Elem - def nullTop: Elem - def absentTop: Elem - - /** constructors */ - def apply( - clo: AbsClo = AbsClo.Bot, - cont: AbsCont = AbsCont.Bot, - part: AbsPart = AbsPart.Bot, - astValue: AbsAstValue = AbsAstValue.Bot, - nt: AbsNt = AbsNt.Bot, - codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, - const: AbsConst = AbsConst.Bot, - math: AbsMath = AbsMath.Bot, - simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, - number: AbsNumber = AbsNumber.Bot, - bigInt: AbsBigInt = AbsBigInt.Bot, - str: AbsStr = AbsStr.Bot, - bool: AbsBool = AbsBool.Bot, - undef: AbsUndef = AbsUndef.Bot, - nullv: AbsNull = AbsNull.Bot, - absent: AbsAbsent = AbsAbsent.Bot, - ): Elem - - /** raw tuple of each simple value type */ - type RawTuple = ( - AbsClo, - AbsCont, - AbsPart, - AbsAstValue, - AbsNt, - AbsCodeUnit, - AbsConst, - AbsMath, - AbsSimpleValue, - ) - - /** extractors */ - def unapply(elem: Elem): Option[RawTuple] - - /** element interfaces */ - extension (elem: Elem) { - - /** getters */ - def clo: AbsClo - def cont: AbsCont - def part: AbsPart - def astValue: AbsAstValue - def nt: AbsNt - def codeUnit: AbsCodeUnit - def const: AbsConst - def math: AbsMath - def simpleValue: AbsSimpleValue - def number: AbsNumber - def bigInt: AbsBigInt - def str: AbsStr - def bool: AbsBool - def undef: AbsUndef - def nullv: AbsNull - def absent: AbsAbsent - - /** remove absent values */ - def removeAbsent: Elem = elem -- absentTop - - /** get reachable address partitions */ - def reachableParts: Set[Part] - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/pureValue/PureValueBasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/pureValue/PureValueBasicDomain.scala new file mode 100644 index 0000000000..73971c91cd --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/pureValue/PureValueBasicDomain.scala @@ -0,0 +1,237 @@ +package esmeta.analyzer.domain.pureValue + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.* +import esmeta.util.Appender.* + +trait PureValueBasicDomainDecl { self: Self => + + /** basic domain for pure values */ + object PureValueBasicDomain extends PureValueDomain { + + /** elements */ + case class Elem( + clo: AbsClo = AbsClo.Bot, + cont: AbsCont = AbsCont.Bot, + part: AbsPart = AbsPart.Bot, + astValue: AbsAstValue = AbsAstValue.Bot, + nt: AbsNt = AbsNt.Bot, + codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, + const: AbsConst = AbsConst.Bot, + math: AbsMath = AbsMath.Bot, + simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, + ) extends Appendable + + /** top element */ + lazy val Top: Elem = exploded("top abstract pure value") + + /** bottom element */ + val Bot: Elem = Elem() + + /** abstraction functions */ + def alpha(xs: Iterable[APureValue]): Elem = Elem( + AbsClo(xs.collect { case x: AClo => x }), + AbsCont(xs.collect { case x: ACont => x }), + AbsPart(xs.collect { case x: Part => x }), + AbsAstValue(xs.collect { case x: AstValue => x }), + AbsNt(xs.collect { case x: Nt => x }), + AbsCodeUnit(xs.collect { case x: CodeUnit => x }), + AbsConst(xs.collect { case x: Const => x }), + AbsMath(xs.collect { case x: Math => x }), + AbsSimpleValue(xs.collect { case x: SimpleValue => x }), + ) + + /** predefined top values */ + val cloTop: Elem = Bot.copy(clo = AbsClo.Top) + val contTop: Elem = Bot.copy(cont = AbsCont.Top) + val partTop: Elem = Bot.copy(part = AbsPart.Top) + val astValueTop: Elem = Bot.copy(astValue = AbsAstValue.Top) + val ntTop: Elem = Bot.copy(nt = AbsNt.Top) + val codeUnitTop: Elem = Bot.copy(codeUnit = AbsCodeUnit.Top) + val constTop: Elem = Bot.copy(const = AbsConst.Top) + val mathTop: Elem = Bot.copy(math = AbsMath.Top) + val simpleValueTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.Top) + val numberTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.numberTop) + val bigIntTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.bigIntTop) + val strTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.strTop) + val boolTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.boolTop) + val undefTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.undefTop) + val nullTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.nullTop) + val absentTop: Elem = Bot.copy(simpleValue = AbsSimpleValue.absentTop) + + /** constructors */ + def apply( + clo: AbsClo = AbsClo.Bot, + cont: AbsCont = AbsCont.Bot, + part: AbsPart = AbsPart.Bot, + astValue: AbsAstValue = AbsAstValue.Bot, + nt: AbsNt = AbsNt.Bot, + codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, + const: AbsConst = AbsConst.Bot, + math: AbsMath = AbsMath.Bot, + simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, + num: AbsNumber = AbsNumber.Bot, + bigInt: AbsBigInt = AbsBigInt.Bot, + str: AbsStr = AbsStr.Bot, + bool: AbsBool = AbsBool.Bot, + undef: AbsUndef = AbsUndef.Bot, + nullv: AbsNull = AbsNull.Bot, + absent: AbsAbsent = AbsAbsent.Bot, + ): Elem = Elem( + clo, + cont, + part, + astValue, + nt, + codeUnit, + const, + math, + simpleValue ⊔ AbsSimpleValue(num, bigInt, str, bool, undef, nullv, absent), + ) + + /** extractors */ + def unapply(elem: Elem): Option[RawTuple] = Some( + ( + elem.clo, + elem.cont, + elem.part, + elem.astValue, + elem.nt, + elem.codeUnit, + elem.const, + elem.math, + elem.simpleValue, + ), + ) + + /** appender */ + given rule: Rule[Elem] = (app, elem) => { + if (elem.isBottom) app >> "⊥" + else { + var strs = Vector[String]() + if (!elem.clo.isBottom) strs :+= elem.clo.toString + if (!elem.cont.isBottom) strs :+= elem.cont.toString + if (!elem.part.isBottom) strs :+= elem.part.toString + if (!elem.astValue.isBottom) strs :+= elem.astValue.toString + if (!elem.nt.isBottom) strs :+= elem.nt.toString + if (!elem.codeUnit.isBottom) strs :+= elem.codeUnit.toString + if (!elem.const.isBottom) strs :+= elem.const.toString + if (!elem.math.isBottom) strs :+= elem.math.toString + if (!elem.simpleValue.isBottom) strs :+= elem.simpleValue.toString + app >> strs.mkString(", ") + } + } + + /** element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = + elem.clo ⊑ that.clo && + elem.cont ⊑ that.cont && + elem.part ⊑ that.part && + elem.astValue ⊑ that.astValue && + elem.nt ⊑ that.nt && + elem.codeUnit ⊑ that.codeUnit && + elem.const ⊑ that.const && + elem.math ⊑ that.math && + elem.simpleValue ⊑ that.simpleValue + + /** join operator */ + def ⊔(that: Elem): Elem = Elem( + elem.clo ⊔ that.clo, + elem.cont ⊔ that.cont, + elem.part ⊔ that.part, + elem.astValue ⊔ that.astValue, + elem.nt ⊔ that.nt, + elem.codeUnit ⊔ that.codeUnit, + elem.const ⊔ that.const, + elem.math ⊔ that.math, + elem.simpleValue ⊔ that.simpleValue, + ) + + /** meet operator */ + override def ⊓(that: Elem): Elem = Elem( + elem.clo ⊓ that.clo, + elem.cont ⊓ that.cont, + elem.part ⊓ that.part, + elem.astValue ⊓ that.astValue, + elem.nt ⊓ that.nt, + elem.codeUnit ⊓ that.codeUnit, + elem.const ⊓ that.const, + elem.math ⊓ that.math, + elem.simpleValue ⊓ that.simpleValue, + ) + + /** prune operator */ + override def --(that: Elem): Elem = Elem( + elem.clo -- that.clo, + elem.cont -- that.cont, + elem.part -- that.part, + elem.astValue -- that.astValue, + elem.nt -- that.nt, + elem.codeUnit -- that.codeUnit, + elem.const -- that.const, + elem.math -- that.math, + elem.simpleValue -- that.simpleValue, + ) + + /** concretization function */ + override def gamma: BSet[APureValue] = + (elem.clo.gamma: BSet[APureValue]) ⊔ + elem.cont.gamma ⊔ + elem.part.gamma ⊔ + elem.astValue.gamma ⊔ + elem.nt.gamma ⊔ + elem.codeUnit.gamma ⊔ + elem.const.gamma ⊔ + elem.math.gamma ⊔ + elem.simpleValue.gamma + + /** get single value */ + override def getSingle: Flat[APureValue] = + (elem.clo.getSingle: Flat[APureValue]) ⊔ + elem.cont.getSingle ⊔ + elem.part.getSingle ⊔ + elem.astValue.getSingle ⊔ + elem.nt.getSingle ⊔ + elem.codeUnit.getSingle ⊔ + elem.const.getSingle ⊔ + elem.math.getSingle ⊔ + elem.simpleValue.getSingle + + /** getters */ + def clo: AbsClo = elem.clo + def cont: AbsCont = elem.cont + def part: AbsPart = elem.part + def astValue: AbsAstValue = elem.astValue + def nt: AbsNt = elem.nt + def codeUnit: AbsCodeUnit = elem.codeUnit + def const: AbsConst = elem.const + def math: AbsMath = elem.math + def simpleValue: AbsSimpleValue = elem.simpleValue + def number: AbsNumber = elem.simpleValue.number + def bigInt: AbsBigInt = elem.simpleValue.bigInt + def str: AbsStr = elem.simpleValue.str + def bool: AbsBool = elem.simpleValue.bool + def undef: AbsUndef = elem.simpleValue.undef + def nullv: AbsNull = elem.simpleValue.nullv + def absent: AbsAbsent = elem.simpleValue.absent + + /** get reachable address partitions */ + def reachableParts: Set[Part] = + var parts = part.toSet + for { + AClo(_, captured) <- clo + (_, value) <- captured + } parts ++= value.reachableParts + for { + ACont(_, captured) <- cont + (_, value) <- captured + } parts ++= value.reachableParts + parts + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/pureValue/PureValueDomain.scala b/src/main/scala/esmeta/analyzer/domain/pureValue/PureValueDomain.scala new file mode 100644 index 0000000000..52078e216a --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/pureValue/PureValueDomain.scala @@ -0,0 +1,97 @@ +package esmeta.analyzer.domain.pureValue + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait PureValueDomainDecl { self: Self => + + /** abstract pure value (value except completion record) domain */ + trait PureValueDomain extends Domain[APureValue] { + + /** abstraction functions for an original pure value */ + def alpha(pureValue: PureValue): Elem = alpha(APureValue.from(pureValue)) + + /** predefined top values */ + def cloTop: Elem + def contTop: Elem + def partTop: Elem + def astValueTop: Elem + def ntTop: Elem + def codeUnitTop: Elem + def constTop: Elem + def mathTop: Elem + def simpleValueTop: Elem + def numberTop: Elem + def bigIntTop: Elem + def strTop: Elem + def boolTop: Elem + def undefTop: Elem + def nullTop: Elem + def absentTop: Elem + + /** constructors */ + def apply( + clo: AbsClo = AbsClo.Bot, + cont: AbsCont = AbsCont.Bot, + part: AbsPart = AbsPart.Bot, + astValue: AbsAstValue = AbsAstValue.Bot, + nt: AbsNt = AbsNt.Bot, + codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, + const: AbsConst = AbsConst.Bot, + math: AbsMath = AbsMath.Bot, + simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, + number: AbsNumber = AbsNumber.Bot, + bigInt: AbsBigInt = AbsBigInt.Bot, + str: AbsStr = AbsStr.Bot, + bool: AbsBool = AbsBool.Bot, + undef: AbsUndef = AbsUndef.Bot, + nullv: AbsNull = AbsNull.Bot, + absent: AbsAbsent = AbsAbsent.Bot, + ): Elem + + /** raw tuple of each simple value type */ + type RawTuple = ( + AbsClo, + AbsCont, + AbsPart, + AbsAstValue, + AbsNt, + AbsCodeUnit, + AbsConst, + AbsMath, + AbsSimpleValue, + ) + + /** extractors */ + def unapply(elem: Elem): Option[RawTuple] + + /** element interfaces */ + extension (elem: Elem) { + + /** getters */ + def clo: AbsClo + def cont: AbsCont + def part: AbsPart + def astValue: AbsAstValue + def nt: AbsNt + def codeUnit: AbsCodeUnit + def const: AbsConst + def math: AbsMath + def simpleValue: AbsSimpleValue + def number: AbsNumber + def bigInt: AbsBigInt + def str: AbsStr + def bool: AbsBool + def undef: AbsUndef + def nullv: AbsNull + def absent: AbsAbsent + + /** remove absent values */ + def removeAbsent: Elem = elem -- absentTop + + /** get reachable address partitions */ + def reachableParts: Set[Part] + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/pureValue/package.scala b/src/main/scala/esmeta/analyzer/domain/pureValue/package.scala new file mode 100644 index 0000000000..5a1aa7d6db --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/pureValue/package.scala @@ -0,0 +1,9 @@ +package esmeta.analyzer.domain.pureValue + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl extends PureValueDomainDecl with PureValueBasicDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/ret/BasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/ret/BasicDomain.scala deleted file mode 100644 index 47a3adc788..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/ret/BasicDomain.scala +++ /dev/null @@ -1,92 +0,0 @@ -package esmeta.analyzer.domain.ret - -import esmeta.LINE_SEP -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.Appender -import esmeta.util.Appender.* - -/** basic domain for return values */ -object BasicDomain extends ret.Domain { - - /** elements */ - case class Elem( - value: AbsValue = AbsValue.Bot, - state: AbsState = AbsState.Bot, - ) extends Appendable - - /** top element */ - lazy val Top = Elem(AbsValue.Top, AbsState.Top) - - /** bottom element */ - lazy val Bot = Elem() - - /** abstraction functions */ - def alpha(xs: Iterable[(AValue, State)]): Elem = - val (vs, ss) = xs.unzip - Elem(AbsValue(vs), AbsState(ss)) - - /** constructors */ - def apply( - value: AbsValue = AbsValue.Bot, - state: AbsState = AbsState.Bot, - ): Elem = Elem(value, state) - - /** extractors */ - def unapply(elem: Elem): (AbsValue, AbsState) = - val Elem(value, state) = elem - (value, state) - - /** appender */ - given rule: Rule[Elem] = (app, elem) => app >> elem.value - - /** element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = - elem.value ⊑ that.value && - elem.state ⊑ that.state - - /** join operator */ - def ⊔(that: Elem): Elem = Elem( - elem.value ⊔ that.value, - elem.state ⊔ that.state, - ) - - /** meet operator */ - override def ⊓(that: Elem): Elem = Elem( - elem.value ⊓ that.value, - elem.state ⊓ that.state, - ) - - /** minus operator */ - override def --(that: Elem): Elem = Elem( - elem.value -- that.value, - elem.state -- that.state, - ) - - /** wrap completion records */ - def wrapCompletion: Elem = Elem(elem.value.wrapCompletion, elem.state) - - /** getters */ - def value: AbsValue = elem.value - def state: AbsState = elem.state - - /** conversion to string */ - def getString(detail: Boolean): String = - val app = Appender() - if (detail) { - app >> elem >> LINE_SEP - app >> "globals: " - app.wrap { - for ((k, v) <- state.globals.toList.sortBy(_._1.toString)) { - app :> s"$k -> $v" >> LINE_SEP - } - } >> LINE_SEP - app >> "heap: " >> state.heap - } else app >> elem - app.toString - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/ret/Domain.scala b/src/main/scala/esmeta/analyzer/domain/ret/Domain.scala deleted file mode 100644 index b8122f5b5a..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/ret/Domain.scala +++ /dev/null @@ -1,32 +0,0 @@ -package esmeta.analyzer.domain.ret - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** abstract return value domain */ -trait Domain extends domain.Domain[(AValue, State)] { - - /** constructors */ - def apply( - value: AbsValue = AbsValue.Bot, - state: AbsState = AbsState.Bot, - ): Elem - - /** extractors */ - def unapply(elem: Elem): (AbsValue, AbsState) - - /** return value element interfaces */ - extension (elem: Elem) { - - /** wrap completion records */ - def wrapCompletion: Elem - - /** getters */ - def value: AbsValue - def state: AbsState - - /** get string */ - def getString(detail: Boolean): String - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/ret/RetBasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/ret/RetBasicDomain.scala new file mode 100644 index 0000000000..52790a83be --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/ret/RetBasicDomain.scala @@ -0,0 +1,95 @@ +package esmeta.analyzer.domain.ret + +import esmeta.LINE_SEP +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.Appender +import esmeta.util.Appender.* + +trait RetBasicDomainDecl { self: Self => + + /** basic domain for return values */ + object RetBasicDomain extends RetDomain { + + /** elements */ + case class Elem( + value: AbsValue = AbsValue.Bot, + state: AbsState = AbsState.Bot, + ) extends Appendable + + /** top element */ + lazy val Top = Elem(AbsValue.Top, AbsState.Top) + + /** bottom element */ + lazy val Bot = Elem() + + /** abstraction functions */ + def alpha(xs: Iterable[(AValue, State)]): Elem = + val (vs, ss) = xs.unzip + Elem(AbsValue(vs), AbsState(ss)) + + /** constructors */ + def apply( + value: AbsValue = AbsValue.Bot, + state: AbsState = AbsState.Bot, + ): Elem = Elem(value, state) + + /** extractors */ + def unapply(elem: Elem): (AbsValue, AbsState) = + val Elem(value, state) = elem + (value, state) + + /** appender */ + given rule: Rule[Elem] = (app, elem) => app >> elem.value + + /** element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = + elem.value ⊑ that.value && + elem.state ⊑ that.state + + /** join operator */ + def ⊔(that: Elem): Elem = Elem( + elem.value ⊔ that.value, + elem.state ⊔ that.state, + ) + + /** meet operator */ + override def ⊓(that: Elem): Elem = Elem( + elem.value ⊓ that.value, + elem.state ⊓ that.state, + ) + + /** minus operator */ + override def --(that: Elem): Elem = Elem( + elem.value -- that.value, + elem.state -- that.state, + ) + + /** wrap completion records */ + def wrapCompletion: Elem = Elem(elem.value.wrapCompletion, elem.state) + + /** getters */ + def value: AbsValue = elem.value + def state: AbsState = elem.state + + /** conversion to string */ + def getString(detail: Boolean): String = + val app = Appender() + if (detail) { + app >> elem >> LINE_SEP + app >> "globals: " + app.wrap { + for ((k, v) <- state.globals.toList.sortBy(_._1.toString)) { + app :> s"$k -> $v" >> LINE_SEP + } + } >> LINE_SEP + app >> "heap: " >> state.heap + } else app >> elem + app.toString + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/ret/RetDomain.scala b/src/main/scala/esmeta/analyzer/domain/ret/RetDomain.scala new file mode 100644 index 0000000000..8f47d9c1f1 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/ret/RetDomain.scala @@ -0,0 +1,35 @@ +package esmeta.analyzer.domain.ret + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait RetDomainDecl { self: Self => + + /** abstract return value domain */ + trait RetDomain extends Domain[(AValue, State)] { + + /** constructors */ + def apply( + value: AbsValue = AbsValue.Bot, + state: AbsState = AbsState.Bot, + ): Elem + + /** extractors */ + def unapply(elem: Elem): (AbsValue, AbsState) + + /** return value element interfaces */ + extension (elem: Elem) { + + /** wrap completion records */ + def wrapCompletion: Elem + + /** getters */ + def value: AbsValue + def state: AbsState + + /** get string */ + def getString(detail: Boolean): String + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/ret/RetTypeDomain.scala b/src/main/scala/esmeta/analyzer/domain/ret/RetTypeDomain.scala new file mode 100644 index 0000000000..b9aecdf877 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/ret/RetTypeDomain.scala @@ -0,0 +1,65 @@ +package esmeta.analyzer.domain.ret + +import esmeta.LINE_SEP +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.Appender +import esmeta.util.Appender.* + +trait RetTypeDomainDecl { self: Self => + + /** type domain for return values */ + object RetTypeDomain extends RetDomain { + + /** elements */ + case class Elem(value: AbsValue = AbsValue.Bot) extends Appendable + + /** top element */ + lazy val Top = Elem(AbsValue.Top) + + /** bottom element */ + lazy val Bot = Elem() + + /** abstraction functions */ + def alpha(xs: Iterable[(AValue, State)]): Elem = ??? + + /** constructors */ + def apply( + value: AbsValue = AbsValue.Bot, + state: AbsState = AbsState.Bot, + ): Elem = Elem(value) + + /** extractors */ + def unapply(elem: Elem): (AbsValue, AbsState) = (elem.value, AbsState.Bot) + + /** appender */ + given rule: Rule[Elem] = (app, elem) => app >> elem.value + + /** element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = elem.value ⊑ that.value + + /** join operator */ + def ⊔(that: Elem): Elem = Elem(elem.value ⊔ that.value) + + /** meet operator */ + override def ⊓(that: Elem): Elem = Elem(elem.value ⊓ that.value) + + /** minus operator */ + override def --(that: Elem): Elem = Elem(elem.value -- that.value) + + /** wrap completion records */ + def wrapCompletion: Elem = Elem(elem.value.wrapCompletion) + + /** getters */ + def value: AbsValue = elem.value + def state: AbsState = AbsState.Bot + + /** conversion to string */ + def getString(detail: Boolean): String = elem.toString + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/ret/TypeDomain.scala b/src/main/scala/esmeta/analyzer/domain/ret/TypeDomain.scala deleted file mode 100644 index 82fde8bd52..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/ret/TypeDomain.scala +++ /dev/null @@ -1,62 +0,0 @@ -package esmeta.analyzer.domain.ret - -import esmeta.LINE_SEP -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.Appender -import esmeta.util.Appender.* - -/** type domain for return values */ -object TypeDomain extends ret.Domain { - - /** elements */ - case class Elem(value: AbsValue = AbsValue.Bot) extends Appendable - - /** top element */ - lazy val Top = Elem(AbsValue.Top) - - /** bottom element */ - lazy val Bot = Elem() - - /** abstraction functions */ - def alpha(xs: Iterable[(AValue, State)]): Elem = ??? - - /** constructors */ - def apply( - value: AbsValue = AbsValue.Bot, - state: AbsState = AbsState.Bot, - ): Elem = Elem(value) - - /** extractors */ - def unapply(elem: Elem): (AbsValue, AbsState) = (elem.value, AbsState.Bot) - - /** appender */ - given rule: Rule[Elem] = (app, elem) => app >> elem.value - - /** element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = elem.value ⊑ that.value - - /** join operator */ - def ⊔(that: Elem): Elem = Elem(elem.value ⊔ that.value) - - /** meet operator */ - override def ⊓(that: Elem): Elem = Elem(elem.value ⊓ that.value) - - /** minus operator */ - override def --(that: Elem): Elem = Elem(elem.value -- that.value) - - /** wrap completion records */ - def wrapCompletion: Elem = Elem(elem.value.wrapCompletion) - - /** getters */ - def value: AbsValue = elem.value - def state: AbsState = AbsState.Bot - - /** conversion to string */ - def getString(detail: Boolean): String = elem.toString - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/ret/package.scala b/src/main/scala/esmeta/analyzer/domain/ret/package.scala new file mode 100644 index 0000000000..3d5e016f4e --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/ret/package.scala @@ -0,0 +1,12 @@ +package esmeta.analyzer.domain.ret + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends RetDomainDecl + with RetBasicDomainDecl + with RetTypeDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/simpleValue/BasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/simpleValue/BasicDomain.scala deleted file mode 100644 index 5cbf407a3a..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/simpleValue/BasicDomain.scala +++ /dev/null @@ -1,174 +0,0 @@ -package esmeta.analyzer.domain.simpleValue - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.* -import esmeta.util.Appender.* - -/** basic domain for simple values */ -object BasicDomain extends simpleValue.Domain { - - /** elements */ - case class Elem( - number: AbsNumber = AbsNumber.Bot, - bigInt: AbsBigInt = AbsBigInt.Bot, - str: AbsStr = AbsStr.Bot, - bool: AbsBool = AbsBool.Bot, - undef: AbsUndef = AbsUndef.Bot, - nullv: AbsNull = AbsNull.Bot, - absent: AbsAbsent = AbsAbsent.Bot, - ) extends Appendable - - /** top element */ - val Top = Elem( - number = AbsNumber.Top, - bigInt = AbsBigInt.Top, - str = AbsStr.Top, - bool = AbsBool.Top, - undef = AbsUndef.Top, - nullv = AbsNull.Top, - absent = AbsAbsent.Top, - ) - - /** bottom element */ - val Bot = Elem() - - /** abstraction functions */ - def alpha(xs: Iterable[SimpleValue]): Elem = Elem( - AbsNumber(xs.collect { case x: Number => x }), - AbsBigInt(xs.collect { case x: BigInt => x }), - AbsStr(xs.collect { case x: Str => x }), - AbsBool(xs.collect { case x: Bool => x }), - AbsUndef(xs.collect { case x: Undef => x }), - AbsNull(xs.collect { case x: Null => x }), - AbsAbsent(xs.collect { case x: Absent => x }), - ) - - /** predefined top values */ - val numberTop: Elem = Bot.copy(number = AbsNumber.Top) - val bigIntTop: Elem = Bot.copy(bigInt = AbsBigInt.Top) - val strTop: Elem = Bot.copy(str = AbsStr.Top) - val boolTop: Elem = Bot.copy(bool = AbsBool.Top) - val undefTop: Elem = Bot.copy(undef = AbsUndef.Top) - val nullTop: Elem = Bot.copy(nullv = AbsNull.Top) - val absentTop: Elem = Bot.copy(absent = AbsAbsent.Top) - - /** constructors */ - def apply( - number: AbsNumber = AbsNumber.Bot, - bigInt: AbsBigInt = AbsBigInt.Bot, - str: AbsStr = AbsStr.Bot, - bool: AbsBool = AbsBool.Bot, - undef: AbsUndef = AbsUndef.Bot, - nullv: AbsNull = AbsNull.Bot, - absent: AbsAbsent = AbsAbsent.Bot, - ): Elem = Elem(number, bigInt, str, bool, undef, nullv, absent) - - /** extractors */ - def unapply(elem: Elem): Option[RawTuple] = Some( - ( - elem.number, - elem.bigInt, - elem.str, - elem.bool, - elem.undef, - elem.nullv, - elem.absent, - ), - ) - - /** appender */ - given rule: Rule[Elem] = (app, elem) => { - if (elem.isBottom) app >> "⊥" - else { - val Elem(number, bigInt, str, bool, undef, nullv, absent) = elem - var strs = Vector[String]() - if (!number.isBottom) strs :+= number.toString - if (!bigInt.isBottom) strs :+= bigInt.toString - if (!str.isBottom) strs :+= str.toString - if (!bool.isBottom) strs :+= bool.toString - if (!undef.isBottom) strs :+= undef.toString - if (!nullv.isBottom) strs :+= nullv.toString - if (!absent.isBottom) strs :+= absent.toString - app >> strs.mkString(", ") - } - } - - /** element interfaces */ - extension (elem: Elem) { - - /** partial order */ - def ⊑(that: Elem): Boolean = ( - elem.number ⊑ that.number && - elem.bigInt ⊑ that.bigInt && - elem.str ⊑ that.str && - elem.bool ⊑ that.bool && - elem.undef ⊑ that.undef && - elem.nullv ⊑ that.nullv && - elem.absent ⊑ that.absent - ) - - /** join operator */ - def ⊔(that: Elem): Elem = Elem( - elem.number ⊔ that.number, - elem.bigInt ⊔ that.bigInt, - elem.str ⊔ that.str, - elem.bool ⊔ that.bool, - elem.undef ⊔ that.undef, - elem.nullv ⊔ that.nullv, - elem.absent ⊔ that.absent, - ) - - /** meet operator */ - override def ⊓(that: Elem): Elem = Elem( - elem.number ⊓ that.number, - elem.bigInt ⊓ that.bigInt, - elem.str ⊓ that.str, - elem.bool ⊓ that.bool, - elem.undef ⊓ that.undef, - elem.nullv ⊓ that.nullv, - elem.absent ⊓ that.absent, - ) - - /** minus operator */ - override def --(that: Elem): Elem = Elem( - elem.number -- that.number, - elem.bigInt -- that.bigInt, - elem.str -- that.str, - elem.bool -- that.bool, - elem.undef -- that.undef, - elem.nullv -- that.nullv, - elem.absent -- that.absent, - ) - - /** concretization function */ - override def gamma: BSet[SimpleValue] = - elem.number.gamma ⊔ - elem.bigInt.gamma ⊔ - elem.str.gamma ⊔ - elem.bool.gamma ⊔ - elem.undef.gamma ⊔ - elem.nullv.gamma ⊔ - elem.absent.gamma - - /** get single value */ - override def getSingle: Flat[SimpleValue] = - elem.number.getSingle ⊔ - elem.bigInt.getSingle ⊔ - elem.str.getSingle ⊔ - elem.bool.getSingle ⊔ - elem.undef.getSingle ⊔ - elem.nullv.getSingle ⊔ - elem.absent.getSingle - - /** getters */ - def number: AbsNumber = elem.number - def bigInt: AbsBigInt = elem.bigInt - def str: AbsStr = elem.str - def bool: AbsBool = elem.bool - def undef: AbsUndef = elem.undef - def nullv: AbsNull = elem.nullv - def absent: AbsAbsent = elem.absent - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/simpleValue/Domain.scala b/src/main/scala/esmeta/analyzer/domain/simpleValue/Domain.scala deleted file mode 100644 index 4c7170a3c6..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/simpleValue/Domain.scala +++ /dev/null @@ -1,66 +0,0 @@ -package esmeta.analyzer.domain.simpleValue - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.Appender.* - -/** abstract domain for simple values */ -trait Domain extends domain.Domain[SimpleValue] { - - /** element constructors with Scala values */ - def apply(num: Double): Elem = alpha(Number(num)) - def apply(str: String): Elem = alpha(Str(str)) - def apply(bigInt: scala.math.BigInt): Elem = alpha(BigInt(bigInt)) - def apply(bool: Boolean): Elem = alpha(Bool(bool)) - - /** predefined top values */ - def numberTop: Elem - def bigIntTop: Elem - def strTop: Elem - def boolTop: Elem - def undefTop: Elem - def nullTop: Elem - def absentTop: Elem - - /** constructors */ - def apply( - num: AbsNumber = AbsNumber.Bot, - bigInt: AbsBigInt = AbsBigInt.Bot, - str: AbsStr = AbsStr.Bot, - bool: AbsBool = AbsBool.Bot, - undef: AbsUndef = AbsUndef.Bot, - nullv: AbsNull = AbsNull.Bot, - absent: AbsAbsent = AbsAbsent.Bot, - ): Elem - - /** raw tuple of each simple value type */ - type RawTuple = ( - AbsNumber, - AbsBigInt, - AbsStr, - AbsBool, - AbsUndef, - AbsNull, - AbsAbsent, - ) - - /** extractors */ - def unapply(elem: Elem): Option[RawTuple] - - /** simple value element interfaces */ - extension (elem: Elem) { - - /** remove absent values */ - def removeAbsent: Elem = elem -- absentTop - - /** getters */ - def number: AbsNumber - def bigInt: AbsBigInt - def str: AbsStr - def bool: AbsBool - def undef: AbsUndef - def nullv: AbsNull - def absent: AbsAbsent - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/simpleValue/SimpleValueBasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/simpleValue/SimpleValueBasicDomain.scala new file mode 100644 index 0000000000..50684b105b --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/simpleValue/SimpleValueBasicDomain.scala @@ -0,0 +1,177 @@ +package esmeta.analyzer.domain.simpleValue + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.* +import esmeta.util.Appender.* + +trait SimpleValueBasicDomainDecl { self: Self => + + /** basic domain for simple values */ + object SimpleValueBasicDomain extends SimpleValueDomain { + + /** elements */ + case class Elem( + number: AbsNumber = AbsNumber.Bot, + bigInt: AbsBigInt = AbsBigInt.Bot, + str: AbsStr = AbsStr.Bot, + bool: AbsBool = AbsBool.Bot, + undef: AbsUndef = AbsUndef.Bot, + nullv: AbsNull = AbsNull.Bot, + absent: AbsAbsent = AbsAbsent.Bot, + ) extends Appendable + + /** top element */ + val Top = Elem( + number = AbsNumber.Top, + bigInt = AbsBigInt.Top, + str = AbsStr.Top, + bool = AbsBool.Top, + undef = AbsUndef.Top, + nullv = AbsNull.Top, + absent = AbsAbsent.Top, + ) + + /** bottom element */ + val Bot = Elem() + + /** abstraction functions */ + def alpha(xs: Iterable[SimpleValue]): Elem = Elem( + AbsNumber(xs.collect { case x: Number => x }), + AbsBigInt(xs.collect { case x: BigInt => x }), + AbsStr(xs.collect { case x: Str => x }), + AbsBool(xs.collect { case x: Bool => x }), + AbsUndef(xs.collect { case x: Undef => x }), + AbsNull(xs.collect { case x: Null => x }), + AbsAbsent(xs.collect { case x: Absent => x }), + ) + + /** predefined top values */ + val numberTop: Elem = Bot.copy(number = AbsNumber.Top) + val bigIntTop: Elem = Bot.copy(bigInt = AbsBigInt.Top) + val strTop: Elem = Bot.copy(str = AbsStr.Top) + val boolTop: Elem = Bot.copy(bool = AbsBool.Top) + val undefTop: Elem = Bot.copy(undef = AbsUndef.Top) + val nullTop: Elem = Bot.copy(nullv = AbsNull.Top) + val absentTop: Elem = Bot.copy(absent = AbsAbsent.Top) + + /** constructors */ + def apply( + number: AbsNumber = AbsNumber.Bot, + bigInt: AbsBigInt = AbsBigInt.Bot, + str: AbsStr = AbsStr.Bot, + bool: AbsBool = AbsBool.Bot, + undef: AbsUndef = AbsUndef.Bot, + nullv: AbsNull = AbsNull.Bot, + absent: AbsAbsent = AbsAbsent.Bot, + ): Elem = Elem(number, bigInt, str, bool, undef, nullv, absent) + + /** extractors */ + def unapply(elem: Elem): Option[RawTuple] = Some( + ( + elem.number, + elem.bigInt, + elem.str, + elem.bool, + elem.undef, + elem.nullv, + elem.absent, + ), + ) + + /** appender */ + given rule: Rule[Elem] = (app, elem) => { + if (elem.isBottom) app >> "⊥" + else { + val Elem(number, bigInt, str, bool, undef, nullv, absent) = elem + var strs = Vector[String]() + if (!number.isBottom) strs :+= number.toString + if (!bigInt.isBottom) strs :+= bigInt.toString + if (!str.isBottom) strs :+= str.toString + if (!bool.isBottom) strs :+= bool.toString + if (!undef.isBottom) strs :+= undef.toString + if (!nullv.isBottom) strs :+= nullv.toString + if (!absent.isBottom) strs :+= absent.toString + app >> strs.mkString(", ") + } + } + + /** element interfaces */ + extension (elem: Elem) { + + /** partial order */ + def ⊑(that: Elem): Boolean = ( + elem.number ⊑ that.number && + elem.bigInt ⊑ that.bigInt && + elem.str ⊑ that.str && + elem.bool ⊑ that.bool && + elem.undef ⊑ that.undef && + elem.nullv ⊑ that.nullv && + elem.absent ⊑ that.absent + ) + + /** join operator */ + def ⊔(that: Elem): Elem = Elem( + elem.number ⊔ that.number, + elem.bigInt ⊔ that.bigInt, + elem.str ⊔ that.str, + elem.bool ⊔ that.bool, + elem.undef ⊔ that.undef, + elem.nullv ⊔ that.nullv, + elem.absent ⊔ that.absent, + ) + + /** meet operator */ + override def ⊓(that: Elem): Elem = Elem( + elem.number ⊓ that.number, + elem.bigInt ⊓ that.bigInt, + elem.str ⊓ that.str, + elem.bool ⊓ that.bool, + elem.undef ⊓ that.undef, + elem.nullv ⊓ that.nullv, + elem.absent ⊓ that.absent, + ) + + /** minus operator */ + override def --(that: Elem): Elem = Elem( + elem.number -- that.number, + elem.bigInt -- that.bigInt, + elem.str -- that.str, + elem.bool -- that.bool, + elem.undef -- that.undef, + elem.nullv -- that.nullv, + elem.absent -- that.absent, + ) + + /** concretization function */ + override def gamma: BSet[SimpleValue] = + elem.number.gamma ⊔ + elem.bigInt.gamma ⊔ + elem.str.gamma ⊔ + elem.bool.gamma ⊔ + elem.undef.gamma ⊔ + elem.nullv.gamma ⊔ + elem.absent.gamma + + /** get single value */ + override def getSingle: Flat[SimpleValue] = + elem.number.getSingle ⊔ + elem.bigInt.getSingle ⊔ + elem.str.getSingle ⊔ + elem.bool.getSingle ⊔ + elem.undef.getSingle ⊔ + elem.nullv.getSingle ⊔ + elem.absent.getSingle + + /** getters */ + def number: AbsNumber = elem.number + def bigInt: AbsBigInt = elem.bigInt + def str: AbsStr = elem.str + def bool: AbsBool = elem.bool + def undef: AbsUndef = elem.undef + def nullv: AbsNull = elem.nullv + def absent: AbsAbsent = elem.absent + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/simpleValue/SimpleValueDomain.scala b/src/main/scala/esmeta/analyzer/domain/simpleValue/SimpleValueDomain.scala new file mode 100644 index 0000000000..1d2eb47ef7 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/simpleValue/SimpleValueDomain.scala @@ -0,0 +1,69 @@ +package esmeta.analyzer.domain.simpleValue + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.Appender.* + +trait SimpleValueDomainDecl { self: Self => + + /** abstract domain for simple values */ + trait SimpleValueDomain extends Domain[SimpleValue] { + + /** element constructors with Scala values */ + def apply(num: Double): Elem = alpha(Number(num)) + def apply(str: String): Elem = alpha(Str(str)) + def apply(bigInt: scala.math.BigInt): Elem = alpha(BigInt(bigInt)) + def apply(bool: Boolean): Elem = alpha(Bool(bool)) + + /** predefined top values */ + def numberTop: Elem + def bigIntTop: Elem + def strTop: Elem + def boolTop: Elem + def undefTop: Elem + def nullTop: Elem + def absentTop: Elem + + /** constructors */ + def apply( + num: AbsNumber = AbsNumber.Bot, + bigInt: AbsBigInt = AbsBigInt.Bot, + str: AbsStr = AbsStr.Bot, + bool: AbsBool = AbsBool.Bot, + undef: AbsUndef = AbsUndef.Bot, + nullv: AbsNull = AbsNull.Bot, + absent: AbsAbsent = AbsAbsent.Bot, + ): Elem + + /** raw tuple of each simple value type */ + type RawTuple = ( + AbsNumber, + AbsBigInt, + AbsStr, + AbsBool, + AbsUndef, + AbsNull, + AbsAbsent, + ) + + /** extractors */ + def unapply(elem: Elem): Option[RawTuple] + + /** simple value element interfaces */ + extension (elem: Elem) { + + /** remove absent values */ + def removeAbsent: Elem = elem -- absentTop + + /** getters */ + def number: AbsNumber + def bigInt: AbsBigInt + def str: AbsStr + def bool: AbsBool + def undef: AbsUndef + def nullv: AbsNull + def absent: AbsAbsent + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/simpleValue/package.scala b/src/main/scala/esmeta/analyzer/domain/simpleValue/package.scala new file mode 100644 index 0000000000..5c67ae384a --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/simpleValue/package.scala @@ -0,0 +1,9 @@ +package esmeta.analyzer.domain.simpleValue + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl extends SimpleValueDomainDecl with SimpleValueBasicDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/state/BasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/state/BasicDomain.scala deleted file mode 100644 index d810ae74f1..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/state/BasicDomain.scala +++ /dev/null @@ -1,436 +0,0 @@ -package esmeta.analyzer.domain.state - -import esmeta.LINE_SEP -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.cfg.CFG -import esmeta.ir.{*, given} -import esmeta.es -import esmeta.es.* -import esmeta.state.* -import esmeta.util.* -import esmeta.util.Appender -import esmeta.util.Appender.{*, given} -import esmeta.util.BaseUtils.* - -/** basic domain for states */ -object BasicDomain extends state.Domain { - - /** elements */ - case class Elem( - reachable: Boolean = false, - locals: Map[Local, AbsValue] = Map(), - globals: Map[Global, AbsValue] = Map(), - heap: AbsHeap = AbsHeap.Bot, - ) extends Appendable - - /** top element */ - lazy val Top: Elem = exploded("top abstract state") - - /** bottom element */ - val Bot: Elem = Elem() - - /** empty element */ - val Empty: Elem = Elem(reachable = true) - - /** abstraction functions */ - def alpha(xs: Iterable[State]): Elem = Top - - /** appender */ - given rule: Rule[Elem] = mkRule(true) - - /** simpler appender */ - private val shortRule: Rule[Elem] = mkRule(false) - - /** set bases */ - def setBase(init: Initialize): Unit = - AbsHeap.setBase(init.initHeap) - base = for ((x, v) <- init.initGlobal.toMap) yield x -> AbsValue(v) - - private var base: Map[Id, AbsValue] = Map() - - /** element interfaces */ - extension (elem: Elem) { - - /** bottom check */ - override def isBottom = !elem.reachable - - /** partial order */ - def ⊑(that: Elem): Boolean = (elem, that) match - case _ if elem.isBottom => true - case _ if that.isBottom => false - case ( - Elem(_, llocals, lglobals, lheap), - Elem(_, rlocals, rglobals, rheap), - ) => - val localsB = (llocals.keySet ++ rlocals.keySet).forall(x => { - elem.lookupLocal(x) ⊑ that.lookupLocal(x) - }) - val globalsB = (lglobals.keySet ++ rglobals.keySet).forall(x => { - elem.lookupGlobal(x) ⊑ that.lookupGlobal(x) - }) - val heapB = lheap ⊑ rheap - localsB && globalsB && heapB - - /** join operator */ - def ⊔(that: Elem): Elem = (elem, that) match - case _ if elem.isBottom => that - case _ if that.isBottom => elem - case (l, r) => - val newLocals = (for { - x <- (l.locals.keySet ++ r.locals.keySet).toList - v = elem.lookupLocal(x) ⊔ that.lookupLocal(x) - } yield x -> v).toMap - val newGlobals = (for { - x <- (l.globals.keySet ++ r.globals.keySet).toList - v = elem.lookupGlobal(x) ⊔ that.lookupGlobal(x) - } yield x -> v).toMap - val newHeap = elem.heap ⊔ that.heap - Elem(true, newLocals, newGlobals, newHeap) - - /** meet operator */ - override def ⊓(that: Elem): Elem = (elem, that) match - case _ if elem.isBottom || that.isBottom => Bot - case (l, r) => - var isBottom = false - val newLocals = (for { - x <- (l.locals.keySet ++ r.locals.keySet).toList - v = elem.lookupLocal(x) ⊓ that.lookupLocal(x) - _ = isBottom ||= v.isBottom - } yield x -> v).toMap - val newGlobals = (for { - x <- (l.globals.keySet ++ r.globals.keySet).toList - v = elem.lookupGlobal(x) ⊓ that.lookupGlobal(x) - _ = isBottom ||= v.isBottom - } yield x -> v).toMap - val newHeap = elem.heap ⊓ that.heap - if (newHeap.isBottom || isBottom) Bot - else Elem(true, newLocals, newGlobals, newHeap) - - /** getters with bases and properties */ - def get(base: AbsValue, prop: AbsValue): AbsValue = - val compValue = AbsValue(pureValue = base.comp(prop.str)) - val partValue = elem.heap(base.part, prop) - val astValue = lookupAst(base.astValue, prop) - val strValue = lookupStr(base.str, prop) - compValue ⊔ partValue ⊔ astValue ⊔ strValue - - /** getters with an address partition */ - def get(part: Part): AbsObj = elem.heap(part) - - /** lookup global variables */ - def lookupGlobal(x: Global): AbsValue = - elem.globals.getOrElse(x, base.getOrElse(x, AbsValue.Bot)) - - /** identifier setter */ - def update(x: Id, value: AbsValue): Elem = - elem.bottomCheck(AbsValue)(value) { - x match - case x: Global if globals contains x => - elem.copy(globals = globals + (x -> value)) - case x: Name if locals contains x => - elem.copy(locals = locals + (x -> value)) - case x: Temp => - elem.copy(locals = locals + (x -> value)) - case _ => Bot - } - - /** property setter */ - def update(base: AbsValue, prop: AbsValue, value: AbsValue): Elem = - elem.bottomCheck(AbsValue)(base, prop, value) { - elem.copy(heap = elem.heap.update(base.part, prop, value)) - } - - /** deletion with reference values */ - def delete(refV: AbsRefValue): Elem = refV match - case AbsRefId(x) => error(s"cannot delete variable $x") - case AbsRefProp(base, prop) => - elem.bottomCheck(AbsValue)(base, prop) { - elem.copy(heap = elem.heap.delete(base.part, prop)) - } - - /** push values to a list */ - def push(list: AbsValue, value: AbsValue, front: Boolean): Elem = - elem.bottomCheck(AbsValue)(list, value) { - if (front) elem.copy(heap = elem.heap.prepend(list.part, value)) - else elem.copy(heap = elem.heap.append(list.part, value)) - } - - /** remove a value in a list */ - def remove(list: AbsValue, value: AbsValue): Elem = - elem.bottomCheck(AbsValue)(list, value) { - elem.copy(heap = elem.heap.remove(list.part, value)) - } - - /** pop a value in a list */ - def pop(list: AbsValue, front: Boolean): (AbsValue, Elem) = - var v: AbsValue = AbsValue.Bot - val st: Elem = elem.bottomCheck(AbsPart)(list.part) { - val (newV, newH) = elem.heap.pop(list.part, front) - v ⊔= newV - elem.copy(heap = newH) - } - (v, st) - - /** set a type to an address partition */ - def setType(v: AbsValue, tname: String): (AbsValue, Elem) = - elem.bottomCheck(AbsPart)(v.part) { - (v, elem.copy(heap = elem.heap.setType(v.part, tname))) - } - - /** copy object */ - def copyObj(to: AllocSite, from: AbsValue): (AbsValue, Elem) = - val partV = AbsValue(to) - elem.bottomCheck(AbsPart)(from.part) { - (partV, elem.copy(heap = elem.heap.copyObj(from.part)(to))) - } - - /** get object keys */ - def keys( - to: AllocSite, - v: AbsValue, - intSorted: Boolean, - ): (AbsValue, Elem) = - val partV = AbsValue(to) - elem.bottomCheck(AbsPart)(v.part) { - (partV, elem.copy(heap = elem.heap.keys(v.part, intSorted)(to))) - } - - /** list concatenation */ - def concat( - to: AllocSite, - lists: Iterable[AbsValue] = Nil, - ): (AbsValue, Elem) = - elem.bottomCheck(AbsValue)(lists) { - import monad.* - (for { - partV <- id(_.allocList(to)) - part = partV.part - _ <- join(for { - list <- lists - } yield modify(st => st.copy(heap = st.heap.concat(part, list)))) - } yield partV)(elem) - } - - /** get childeren of AST */ - def getChildren( - to: AllocSite, - ast: AbsValue, - ): (AbsValue, Elem) = ast.getSingle match - case One(AstValue(syn: Syntactic)) => - val vs = syn.children.flatten.map(AbsValue(_)) - allocList(to, vs) - case Many => exploded("EGetChildren") - case _ => (AbsValue.Bot, Bot) - - /** get items of AST */ - def getItems( - to: AllocSite, - nt: AbsValue, - ast: AbsValue, - ): (AbsValue, Elem) = (nt.getSingle, ast.getSingle) match - case (One(Nt(name, _)), One(AstValue(ast))) => - val vs = ast.getItems(name).map(AbsValue(_)) - allocList(to, vs) - case (Many, _) | (_, Many) => exploded("EGetItems") - case _ => (AbsValue.Bot, Bot) - - /** allocation of map with address partitions */ - def allocMap( - to: AllocSite, - tname: String, - pairs: Iterable[(AbsValue, AbsValue)], - ): (AbsValue, Elem) = - val partV = AbsValue(to) - elem.bottomCheck(AbsValue)(pairs.flatMap { case (k, v) => List(k, v) }) { - (partV, elem.copy(heap = heap.allocMap(to, tname, pairs))) - } - - /** allocation of list with address partitions */ - def allocList( - to: AllocSite, - list: Iterable[AbsValue] = Nil, - ): (AbsValue, Elem) = - val partV = AbsValue(to) - elem.bottomCheck(AbsValue)(list) { - (partV, elem.copy(heap = heap.allocList(to, list))) - } - - /** allocation of symbol with address partitions */ - def allocSymbol(to: AllocSite, desc: AbsValue): (AbsValue, Elem) = - val partV = AbsValue(to) - val descV = AbsValue(str = desc.str, undef = desc.undef) - elem.bottomCheck(AbsValue)(descV) { - (partV, elem.copy(heap = elem.heap.allocSymbol(to, descV))) - } - - /** check contains */ - def contains(list: AbsValue, value: AbsValue): AbsValue = - heap.contains(list.part, value) - - /** define global variables */ - def defineGlobal(pairs: (Global, AbsValue)*): Elem = - elem.bottomCheck(AbsValue)(pairs.unzip._2) { - elem.copy(globals = elem.globals ++ pairs) - } - - /** define local variables */ - def defineLocal(pairs: (Local, AbsValue)*): Elem = - elem.bottomCheck(AbsValue)(pairs.unzip._2) { - elem.copy(locals = elem.locals ++ pairs) - } - - /** singleton checks */ - override def isSingle: Boolean = - reachable && - locals.forall { case (_, v) => v.isSingle } && - globals.forall { case (_, v) => v.isSingle } && - heap.isSingle - - /** singleton address partition checks */ - def isSingle(part: Part): Boolean = elem.heap.isSingle(part) - - /** find merged parts */ - def findMerged: Unit = { - // visited address partitions - var visited = Set[Part]() - - // heap members - val heap @ AbsHeap(map, merged) = elem.heap - - // auxiliary functions for values - def auxValue( - value: AbsValue, - path: String, - partPath: String = "", - ): Unit = - if (!value.isBottom && !value.isSingle) - if (partPath == "") println(s"$path is merged: $value") - else println(s"$path ($partPath) is merged: $value") - for (part <- value.reachableParts) auxPart(part, path) - - // auxiliary functions for address partitions - def auxPart(part: Part, path: String): Unit = - if (!visited.contains(part) && map.contains(part)) - visited += part - heap(part).findMerged(part, path, auxValue) - - for ((x, v) <- locals) auxValue(v, s"partal $x") - for ((x, v) <- globals) auxValue(v, s"global $x") - for (part <- map.keys if part.isNamed) auxPart(part, s"$part") - for (part <- map.keys if !part.isNamed) - auxPart(part, s"") - } - - /** handle calls */ - def doCall: Elem = elem - .copy(heap = heap.doCall) - .garbageCollected - def doProcStart(fixed: Set[Part]): Elem = elem - .copy(heap = heap.doProcStart(fixed)) - .garbageCollected - - /** handle returns (elem: return states / to: caller states) */ - def doReturn(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem = Elem( - reachable = true, - locals = to.locals ++ defs, - globals = globals, - heap = heap.doReturn(to.heap), - ).garbageCollected - def doProcEnd(to: Elem, defs: (Local, AbsValue)*): Elem = - doProcEnd(to, defs) - def doProcEnd(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem = Elem( - reachable = true, - locals = to.locals ++ defs, - globals = globals, - heap = heap.doProcEnd(to.heap), - ).garbageCollected - - // TODO garbage collection - def garbageCollected: Elem = elem - // if (USE_GC) { - // val unreachParts = (heap.map.keySet -- reachableParts).filter(!_.isNamed) - // copy(heap = heap.removeParts(unreachParts)) - // } else elem - - /** get reachable address partitions */ - def reachableParts: Set[Part] = - var parts = Set[Part]() - for ((_, v) <- locals) parts ++= v.reachableParts - for ((_, v) <- globals) parts ++= v.reachableParts - heap.reachableParts(parts) - - /** copy */ - def copied( - locals: Map[Local, AbsValue] = Map(), - ): Elem = elem.copy(locals = locals) - - /** get string */ - def getString(detail: Boolean): String = - val app = Appender() - given Rule[Elem] = if (detail) rule else shortRule - app >> elem - app.toString - - /** get string with detailed shapes of locations */ - def getString(value: AbsValue): String = - val app = Appender() - app >> value.toString - val parts = value.reachableParts - if (!parts.isEmpty) (app >> " @ ").wrap(for (part <- parts) { - val obj = heap(part) - app :> s"$part -> " >> obj >> LINE_SEP - }) - app.toString - - /** getters */ - def reachable: Boolean = elem.reachable - def locals: Map[Local, AbsValue] = elem.locals - def globals: Map[Global, AbsValue] = elem.globals - def heap: AbsHeap = elem.heap - } - // --------------------------------------------------------------------------- - // private helpers - // --------------------------------------------------------------------------- - // appender generator - private def mkRule(detail: Boolean): Rule[Elem] = (app, elem) => - val irStringifier = IRElem.getStringifier(detail, false) - import irStringifier.given - given Rule[AbsHeap] = if (detail) AbsHeap.rule else AbsHeap.shortRule - if (!elem.isBottom) app.wrap { - app :> "locals: " >> elem.locals >> LINE_SEP - app :> "globals: " >> elem.globals >> LINE_SEP - app :> "heaps: " >> elem.heap >> LINE_SEP - } - else app >> "⊥" - - // lookup ASTs - private def lookupAst(ast: AbsAstValue, prop: AbsValue): AbsValue = - (ast.getSingle, prop.getSingle) match - case (Zero, _) | (_, Zero) => AbsValue.Bot - case (One(AstValue(ast)), One(Str("parent"))) => - ast.parent.map(AbsValue(_)).getOrElse(AbsValue(Absent)) - case (One(AstValue(syn: es.Syntactic)), One(Str(propStr))) => - val es.Syntactic(name, _, rhsIdx, children) = syn - val rhs = cfg.grammar.nameMap(name).rhsList(rhsIdx) - rhs.getNtIndex(propStr).flatMap(children(_)) match - case Some(child) => AbsValue(child) - case _ => AbsValue.Bot - case (One(AstValue(syn: es.Syntactic)), One(Math(n))) if n.isValidInt => - syn.children(n.toInt).map(AbsValue(_)).getOrElse(AbsValue(Absent)) - case (_: One[_], _: One[_]) => AbsValue.Bot - case _ => exploded("ast property access") - - // lookup strings - private def lookupStr(str: AbsStr, prop: AbsValue): AbsValue = - (str.getSingle, prop.getSingle) match - case (Zero, _) | (_, Zero) => AbsValue.Bot - case (One(Str(str)), One(Math(k))) => - AbsValue(CodeUnit(str(k.toInt))) - case (One(Str(str)), One(simple: SimpleValue)) => - simple match - case Str("length") => AbsValue(Math(str.length)) - case Number(k) => AbsValue(CodeUnit(str(k.toInt))) - case _ => AbsValue.Bot - case _ => AbsValue.codeUnitTop ⊔ AbsValue.mathTop -} diff --git a/src/main/scala/esmeta/analyzer/domain/state/Domain.scala b/src/main/scala/esmeta/analyzer/domain/state/Domain.scala deleted file mode 100644 index 85a6ca265b..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/state/Domain.scala +++ /dev/null @@ -1,203 +0,0 @@ -package esmeta.analyzer.domain.state - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.es.Initialize -import esmeta.ir.* -import esmeta.state.* -import esmeta.util.Appender.{*, given} -import esmeta.util.BaseUtils.* -import esmeta.util.StateMonad - -/** abstract state domain */ -trait Domain extends domain.Domain[State] { - - /** empty state */ - def Empty: Elem - - /** monad helper */ - val monad: StateMonad[Elem] = StateMonad[Elem]() - - /** set bases */ - def setBase(init: Initialize): Unit - - /** abstract state interfaces */ - extension (elem: Elem) { - - /** getters */ - def get( - rv: AbsRefValue, - cp: ControlPoint, - ): AbsValue = rv match - case AbsRefId(x) => elem.get(x, cp) - case AbsRefProp(base, prop) => elem.get(base, prop) - - /** getters with identifiers */ - def get(x: Id, cp: ControlPoint): AbsValue = - val v = directLookup(x) - if (cp.isBuiltin && AbsValue.absentTop ⊑ v) - v.removeAbsent ⊔ AbsValue.undefTop - else v - - /** getters with bases and properties */ - def get(base: AbsValue, prop: AbsValue): AbsValue - - /** getters with an address partition */ - def get(part: Part): AbsObj - - /** lookup variables */ - def directLookup(x: Id): AbsValue = x match - case x: Local => lookupLocal(x) - case x: Global => lookupGlobal(x) - - /** lookup local variables */ - def lookupLocal(x: Local): AbsValue = - elem.locals.getOrElse(x, AbsValue.Bot) - - /** lookup global variables */ - def lookupGlobal(x: Global): AbsValue - - /** existence checks */ - def exists(ref: AbsRefValue): AbsValue = ref match - case AbsRefId(id) => !directLookup(id).isAbsent - case AbsRefProp(base, prop) => - !elem.get(base, prop).isAbsent - - /** define local variables */ - def defineLocal(pairs: (Local, AbsValue)*): Elem - - /** define global variables */ - def defineGlobal(pairs: (Global, AbsValue)*): Elem - - /** setter with reference values */ - def update(refV: AbsRefValue, value: AbsValue): Elem = refV match - case AbsRefId(x) => update(x, value) - case AbsRefProp(base, prop) => update(base, prop, value) - - /** identifier setter */ - def update(x: Id, value: AbsValue): Elem - - /** property setter */ - def update(base: AbsValue, prop: AbsValue, value: AbsValue): Elem - - /** deletion with reference values */ - def delete(refV: AbsRefValue): Elem - - /** push values to a list */ - def push(list: AbsValue, value: AbsValue, front: Boolean): Elem - - /** remove a value in a list */ - def remove(list: AbsValue, value: AbsValue): Elem - - /** pop a value in a list */ - def pop(list: AbsValue, front: Boolean): (AbsValue, Elem) - - /** set a type to an address partition */ - def setType(part: AbsValue, tname: String): (AbsValue, Elem) - - /** copy object */ - def copyObj(to: AllocSite, from: AbsValue): (AbsValue, Elem) - - /** get object keys */ - def keys( - to: AllocSite, - part: AbsValue, - intSorted: Boolean, - ): (AbsValue, Elem) - - /** list concatenation */ - def concat( - to: AllocSite, - ls: Iterable[AbsValue] = Nil, - ): (AbsValue, Elem) - - /** get childeren of AST */ - def getChildren( - to: AllocSite, - ast: AbsValue, - ): (AbsValue, Elem) - - /** get items of AST */ - def getItems( - to: AllocSite, - nt: AbsValue, - ast: AbsValue, - ): (AbsValue, Elem) - - /** allocation of map with address partitions */ - def allocMap( - to: AllocSite, - tname: String, - pairs: Iterable[(AbsValue, AbsValue)], - ): (AbsValue, Elem) - - /** allocation of list with address partitions */ - def allocList( - to: AllocSite, - list: Iterable[AbsValue] = Nil, - ): (AbsValue, Elem) - - /** allocation of symbol with address partitions */ - def allocSymbol(to: AllocSite, desc: AbsValue): (AbsValue, Elem) - - /** check contains */ - def contains(list: AbsValue, value: AbsValue): AbsValue - - /** find merged parts */ - def findMerged: Unit - - /** handle calls */ - def doCall: Elem - def doProcStart(fixed: Set[Part]): Elem - - /** handle returns (elem: return states / to: caller states) */ - def doReturn(to: Elem, defs: (Local, AbsValue)*): Elem = doReturn(to, defs) - def doReturn(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem - def doProcEnd(to: Elem, defs: (Local, AbsValue)*): Elem - def doProcEnd(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem - def garbageCollected: Elem - - /** get reachable address partitions */ - def reachableParts: Set[Part] - - /** singleton checks */ - def isSingle: Boolean - - /** singleton address partition checks */ - def isSingle(part: Part): Boolean - - /** copy */ - def copied( - locals: Map[Local, AbsValue] = Map(), - ): Elem - - /** conversion to string */ - def getString(detail: Boolean): String - - /** get string with detailed shape of address partitions */ - def getString(value: AbsValue): String - - /** getters */ - def reachable: Boolean - def locals: Map[Local, AbsValue] - def globals: Map[Global, AbsValue] - def heap: AbsHeap - - /** check bottom elements in abstract semantics */ - protected def bottomCheck[A](dom: domain.Domain[A])( - vs: dom.Elem*, - )(f: => Elem): Elem = elem.bottomCheck(dom)(vs)(f) - protected def bottomCheck[A](dom: domain.Domain[_])( - vs: Iterable[dom.Elem], - )(f: => Elem): Elem = - if (elem.isBottom || vs.exists(_.isBottom)) Bot else f - protected def bottomCheck[A](dom: domain.Domain[A])( - vs: dom.Elem*, - )(f: => (AbsValue, Elem)): (AbsValue, Elem) = elem.bottomCheck(dom)(vs)(f) - protected def bottomCheck[A](dom: domain.Domain[A])( - vs: Iterable[dom.Elem], - )(f: => (AbsValue, Elem)): (AbsValue, Elem) = - if (elem.isBottom || vs.exists(_.isBottom)) (AbsValue.Bot, Bot) - else f - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/state/StateBasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/state/StateBasicDomain.scala new file mode 100644 index 0000000000..cefd003278 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/state/StateBasicDomain.scala @@ -0,0 +1,441 @@ +package esmeta.analyzer.domain.state + +import esmeta.LINE_SEP +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.cfg.CFG +import esmeta.ir.{*, given} +import esmeta.es +import esmeta.es.* +import esmeta.state.* +import esmeta.util.* +import esmeta.util.Appender +import esmeta.util.Appender.{*, given} +import esmeta.util.BaseUtils.* + +trait StateBasicDomainDecl { self: Self => + + /** basic domain for states */ + object StateBasicDomain extends StateDomain { + + /** elements */ + case class Elem( + reachable: Boolean = false, + locals: Map[Local, AbsValue] = Map(), + globals: Map[Global, AbsValue] = Map(), + heap: AbsHeap = AbsHeap.Bot, + ) extends Appendable + + /** top element */ + lazy val Top: Elem = exploded("top abstract state") + + /** bottom element */ + val Bot: Elem = Elem() + + /** empty element */ + val Empty: Elem = Elem(reachable = true) + + /** abstraction functions */ + def alpha(xs: Iterable[State]): Elem = Top + + /** appender */ + given rule: Rule[Elem] = mkRule(true) + + /** simpler appender */ + private val shortRule: Rule[Elem] = mkRule(false) + + /** set bases */ + def setBase(init: Initialize): Unit = + AbsHeap.setBase(init.initHeap) + base = for ((x, v) <- init.initGlobal.toMap) yield x -> AbsValue(v) + + private var base: Map[Id, AbsValue] = Map() + + /** element interfaces */ + extension (elem: Elem) { + + /** bottom check */ + override def isBottom = !elem.reachable + + /** partial order */ + def ⊑(that: Elem): Boolean = (elem, that) match + case _ if elem.isBottom => true + case _ if that.isBottom => false + case ( + Elem(_, llocals, lglobals, lheap), + Elem(_, rlocals, rglobals, rheap), + ) => + val localsB = (llocals.keySet ++ rlocals.keySet).forall(x => { + elem.lookupLocal(x) ⊑ that.lookupLocal(x) + }) + val globalsB = (lglobals.keySet ++ rglobals.keySet).forall(x => { + elem.lookupGlobal(x) ⊑ that.lookupGlobal(x) + }) + val heapB = lheap ⊑ rheap + localsB && globalsB && heapB + + /** join operator */ + def ⊔(that: Elem): Elem = (elem, that) match + case _ if elem.isBottom => that + case _ if that.isBottom => elem + case (l, r) => + val newLocals = (for { + x <- (l.locals.keySet ++ r.locals.keySet).toList + v = elem.lookupLocal(x) ⊔ that.lookupLocal(x) + } yield x -> v).toMap + val newGlobals = (for { + x <- (l.globals.keySet ++ r.globals.keySet).toList + v = elem.lookupGlobal(x) ⊔ that.lookupGlobal(x) + } yield x -> v).toMap + val newHeap = elem.heap ⊔ that.heap + Elem(true, newLocals, newGlobals, newHeap) + + /** meet operator */ + override def ⊓(that: Elem): Elem = (elem, that) match + case _ if elem.isBottom || that.isBottom => Bot + case (l, r) => + var isBottom = false + val newLocals = (for { + x <- (l.locals.keySet ++ r.locals.keySet).toList + v = elem.lookupLocal(x) ⊓ that.lookupLocal(x) + _ = isBottom ||= v.isBottom + } yield x -> v).toMap + val newGlobals = (for { + x <- (l.globals.keySet ++ r.globals.keySet).toList + v = elem.lookupGlobal(x) ⊓ that.lookupGlobal(x) + _ = isBottom ||= v.isBottom + } yield x -> v).toMap + val newHeap = elem.heap ⊓ that.heap + if (newHeap.isBottom || isBottom) Bot + else Elem(true, newLocals, newGlobals, newHeap) + + /** getters with bases and properties */ + def get(base: AbsValue, prop: AbsValue): AbsValue = + val compValue = AbsValue(pureValue = base.comp(prop.str)) + val partValue = elem.heap(base.part, prop) + val astValue = lookupAst(base.astValue, prop) + val strValue = lookupStr(base.str, prop) + compValue ⊔ partValue ⊔ astValue ⊔ strValue + + /** getters with an address partition */ + def get(part: Part): AbsObj = elem.heap(part) + + /** lookup global variables */ + def lookupGlobal(x: Global): AbsValue = + elem.globals.getOrElse(x, base.getOrElse(x, AbsValue.Bot)) + + /** identifier setter */ + def update(x: Id, value: AbsValue): Elem = + elem.bottomCheck(AbsValue)(value) { + x match + case x: Global if globals contains x => + elem.copy(globals = globals + (x -> value)) + case x: Name if locals contains x => + elem.copy(locals = locals + (x -> value)) + case x: Temp => + elem.copy(locals = locals + (x -> value)) + case _ => Bot + } + + /** property setter */ + def update(base: AbsValue, prop: AbsValue, value: AbsValue): Elem = + elem.bottomCheck(AbsValue)(base, prop, value) { + elem.copy(heap = elem.heap.update(base.part, prop, value)) + } + + /** deletion with reference values */ + def delete(refV: AbsRefValue): Elem = refV match + case AbsRefId(x) => error(s"cannot delete variable $x") + case AbsRefProp(base, prop) => + elem.bottomCheck(AbsValue)(base, prop) { + elem.copy(heap = elem.heap.delete(base.part, prop)) + } + + /** push values to a list */ + def push(list: AbsValue, value: AbsValue, front: Boolean): Elem = + elem.bottomCheck(AbsValue)(list, value) { + if (front) elem.copy(heap = elem.heap.prepend(list.part, value)) + else elem.copy(heap = elem.heap.append(list.part, value)) + } + + /** remove a value in a list */ + def remove(list: AbsValue, value: AbsValue): Elem = + elem.bottomCheck(AbsValue)(list, value) { + elem.copy(heap = elem.heap.remove(list.part, value)) + } + + /** pop a value in a list */ + def pop(list: AbsValue, front: Boolean): (AbsValue, Elem) = + var v: AbsValue = AbsValue.Bot + val st: Elem = elem.bottomCheck(AbsPart)(list.part) { + val (newV, newH) = elem.heap.pop(list.part, front) + v ⊔= newV + elem.copy(heap = newH) + } + (v, st) + + /** set a type to an address partition */ + def setType(v: AbsValue, tname: String): (AbsValue, Elem) = + elem.bottomCheck(AbsPart)(v.part) { + (v, elem.copy(heap = elem.heap.setType(v.part, tname))) + } + + /** copy object */ + def copyObj(to: AllocSite, from: AbsValue): (AbsValue, Elem) = + val partV = AbsValue(to) + elem.bottomCheck(AbsPart)(from.part) { + (partV, elem.copy(heap = elem.heap.copyObj(from.part)(to))) + } + + /** get object keys */ + def keys( + to: AllocSite, + v: AbsValue, + intSorted: Boolean, + ): (AbsValue, Elem) = + val partV = AbsValue(to) + elem.bottomCheck(AbsPart)(v.part) { + (partV, elem.copy(heap = elem.heap.keys(v.part, intSorted)(to))) + } + + /** list concatenation */ + def concat( + to: AllocSite, + lists: Iterable[AbsValue] = Nil, + ): (AbsValue, Elem) = + elem.bottomCheck(AbsValue)(lists) { + import monad.* + (for { + partV <- id(_.allocList(to)) + part = partV.part + _ <- join(for { + list <- lists + } yield modify(st => st.copy(heap = st.heap.concat(part, list)))) + } yield partV)(elem) + } + + /** get childeren of AST */ + def getChildren( + to: AllocSite, + ast: AbsValue, + ): (AbsValue, Elem) = ast.getSingle match + case One(AstValue(syn: Syntactic)) => + val vs = syn.children.flatten.map(AbsValue(_)) + allocList(to, vs) + case Many => exploded("EGetChildren") + case _ => (AbsValue.Bot, Bot) + + /** get items of AST */ + def getItems( + to: AllocSite, + nt: AbsValue, + ast: AbsValue, + ): (AbsValue, Elem) = (nt.getSingle, ast.getSingle) match + case (One(Nt(name, _)), One(AstValue(ast))) => + val vs = ast.getItems(name).map(AbsValue(_)) + allocList(to, vs) + case (Many, _) | (_, Many) => exploded("EGetItems") + case _ => (AbsValue.Bot, Bot) + + /** allocation of map with address partitions */ + def allocMap( + to: AllocSite, + tname: String, + pairs: Iterable[(AbsValue, AbsValue)], + ): (AbsValue, Elem) = + val partV = AbsValue(to) + elem.bottomCheck(AbsValue)( + pairs.flatMap { case (k, v) => List(k, v) }, + ) { + (partV, elem.copy(heap = heap.allocMap(to, tname, pairs))) + } + + /** allocation of list with address partitions */ + def allocList( + to: AllocSite, + list: Iterable[AbsValue] = Nil, + ): (AbsValue, Elem) = + val partV = AbsValue(to) + elem.bottomCheck(AbsValue)(list) { + (partV, elem.copy(heap = heap.allocList(to, list))) + } + + /** allocation of symbol with address partitions */ + def allocSymbol(to: AllocSite, desc: AbsValue): (AbsValue, Elem) = + val partV = AbsValue(to) + val descV = AbsValue(str = desc.str, undef = desc.undef) + elem.bottomCheck(AbsValue)(descV) { + (partV, elem.copy(heap = elem.heap.allocSymbol(to, descV))) + } + + /** check contains */ + def contains(list: AbsValue, value: AbsValue): AbsValue = + heap.contains(list.part, value) + + /** define global variables */ + def defineGlobal(pairs: (Global, AbsValue)*): Elem = + elem.bottomCheck(AbsValue)(pairs.unzip._2) { + elem.copy(globals = elem.globals ++ pairs) + } + + /** define local variables */ + def defineLocal(pairs: (Local, AbsValue)*): Elem = + elem.bottomCheck(AbsValue)(pairs.unzip._2) { + elem.copy(locals = elem.locals ++ pairs) + } + + /** singleton checks */ + override def isSingle: Boolean = + reachable && + locals.forall { case (_, v) => v.isSingle } && + globals.forall { case (_, v) => v.isSingle } && + heap.isSingle + + /** singleton address partition checks */ + def isSingle(part: Part): Boolean = elem.heap.isSingle(part) + + /** find merged parts */ + def findMerged: Unit = { + // visited address partitions + var visited = Set[Part]() + + // heap members + val heap @ AbsHeap(map, merged) = elem.heap + + // auxiliary functions for values + def auxValue( + value: AbsValue, + path: String, + partPath: String = "", + ): Unit = + if (!value.isBottom && !value.isSingle) + if (partPath == "") println(s"$path is merged: $value") + else println(s"$path ($partPath) is merged: $value") + for (part <- value.reachableParts) auxPart(part, path) + + // auxiliary functions for address partitions + def auxPart(part: Part, path: String): Unit = + if (!visited.contains(part) && map.contains(part)) + visited += part + heap(part).findMerged(part, path, auxValue) + + for ((x, v) <- locals) auxValue(v, s"partal $x") + for ((x, v) <- globals) auxValue(v, s"global $x") + for (part <- map.keys if part.isNamed) auxPart(part, s"$part") + for (part <- map.keys if !part.isNamed) + auxPart(part, s"") + } + + /** handle calls */ + def doCall: Elem = elem + .copy(heap = heap.doCall) + .garbageCollected + def doProcStart(fixed: Set[Part]): Elem = elem + .copy(heap = heap.doProcStart(fixed)) + .garbageCollected + + /** handle returns (elem: return states / to: caller states) */ + def doReturn(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem = Elem( + reachable = true, + locals = to.locals ++ defs, + globals = globals, + heap = heap.doReturn(to.heap), + ).garbageCollected + def doProcEnd(to: Elem, defs: (Local, AbsValue)*): Elem = + doProcEnd(to, defs) + def doProcEnd(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem = Elem( + reachable = true, + locals = to.locals ++ defs, + globals = globals, + heap = heap.doProcEnd(to.heap), + ).garbageCollected + + // TODO garbage collection + def garbageCollected: Elem = elem + // if (USE_GC) { + // val unreachParts = (heap.map.keySet -- reachableParts).filter(!_.isNamed) + // copy(heap = heap.removeParts(unreachParts)) + // } else elem + + /** get reachable address partitions */ + def reachableParts: Set[Part] = + var parts = Set[Part]() + for ((_, v) <- locals) parts ++= v.reachableParts + for ((_, v) <- globals) parts ++= v.reachableParts + heap.reachableParts(parts) + + /** copy */ + def copied( + locals: Map[Local, AbsValue] = Map(), + ): Elem = elem.copy(locals = locals) + + /** get string */ + def getString(detail: Boolean): String = + val app = Appender() + given Rule[Elem] = if (detail) rule else shortRule + app >> elem + app.toString + + /** get string with detailed shapes of locations */ + def getString(value: AbsValue): String = + val app = Appender() + app >> value.toString + val parts = value.reachableParts + if (!parts.isEmpty) (app >> " @ ").wrap(for (part <- parts) { + val obj = heap(part) + app :> s"$part -> " >> obj >> LINE_SEP + }) + app.toString + + /** getters */ + def reachable: Boolean = elem.reachable + def locals: Map[Local, AbsValue] = elem.locals + def globals: Map[Global, AbsValue] = elem.globals + def heap: AbsHeap = elem.heap + } + // ------------------------------------------------------------------------- + // private helpers + // ------------------------------------------------------------------------- + // appender generator + private def mkRule(detail: Boolean): Rule[Elem] = (app, elem) => + val irStringifier = IRElem.getStringifier(detail, false) + import irStringifier.given + given Rule[AbsHeap] = if (detail) AbsHeap.rule else AbsHeap.shortRule + if (!elem.isBottom) app.wrap { + app :> "locals: " >> elem.locals >> LINE_SEP + app :> "globals: " >> elem.globals >> LINE_SEP + app :> "heaps: " >> elem.heap >> LINE_SEP + } + else app >> "⊥" + + // lookup ASTs + private def lookupAst(ast: AbsAstValue, prop: AbsValue): AbsValue = + (ast.getSingle, prop.getSingle) match + case (Zero, _) | (_, Zero) => AbsValue.Bot + case (One(AstValue(ast)), One(Str("parent"))) => + ast.parent.map(AbsValue(_)).getOrElse(AbsValue(Absent)) + case (One(AstValue(syn: es.Syntactic)), One(Str(propStr))) => + val es.Syntactic(name, _, rhsIdx, children) = syn + val rhs = cfg.grammar.nameMap(name).rhsList(rhsIdx) + rhs.getNtIndex(propStr).flatMap(children(_)) match + case Some(child) => AbsValue(child) + case _ => AbsValue.Bot + case (One(AstValue(syn: es.Syntactic)), One(Math(n))) if n.isValidInt => + syn.children(n.toInt).map(AbsValue(_)).getOrElse(AbsValue(Absent)) + case (_: One[_], _: One[_]) => AbsValue.Bot + case _ => exploded("ast property access") + + // lookup strings + private def lookupStr(str: AbsStr, prop: AbsValue): AbsValue = + (str.getSingle, prop.getSingle) match + case (Zero, _) | (_, Zero) => AbsValue.Bot + case (One(Str(str)), One(Math(k))) => + AbsValue(CodeUnit(str(k.toInt))) + case (One(Str(str)), One(simple: SimpleValue)) => + simple match + case Str("length") => AbsValue(Math(str.length)) + case Number(k) => AbsValue(CodeUnit(str(k.toInt))) + case _ => AbsValue.Bot + case _ => AbsValue.codeUnitTop ⊔ AbsValue.mathTop + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/state/StateDomain.scala b/src/main/scala/esmeta/analyzer/domain/state/StateDomain.scala new file mode 100644 index 0000000000..a9211c66c4 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/state/StateDomain.scala @@ -0,0 +1,207 @@ +package esmeta.analyzer.domain.state + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.es.Initialize +import esmeta.ir.* +import esmeta.state.* +import esmeta.util.Appender.{*, given} +import esmeta.util.BaseUtils.* +import esmeta.util.StateMonad + +trait StateDomainDecl { self: Self => + + /** abstract state domain */ + trait StateDomain extends Domain[State] { + + /** empty state */ + def Empty: Elem + + /** monad helper */ + val monad: StateMonad[Elem] = StateMonad[Elem]() + + /** set bases */ + def setBase(init: Initialize): Unit + + /** abstract state interfaces */ + extension (elem: Elem) { + + /** getters */ + def get( + rv: AbsRefValue, + cp: ControlPoint, + ): AbsValue = rv match + case AbsRefId(x) => elem.get(x, cp) + case AbsRefProp(base, prop) => elem.get(base, prop) + + /** getters with identifiers */ + def get(x: Id, cp: ControlPoint): AbsValue = + val v = directLookup(x) + if (cp.isBuiltin && AbsValue.absentTop ⊑ v) + v.removeAbsent ⊔ AbsValue.undefTop + else v + + /** getters with bases and properties */ + def get(base: AbsValue, prop: AbsValue): AbsValue + + /** getters with an address partition */ + def get(part: Part): AbsObj + + /** lookup variables */ + def directLookup(x: Id): AbsValue = x match + case x: Local => lookupLocal(x) + case x: Global => lookupGlobal(x) + + /** lookup local variables */ + def lookupLocal(x: Local): AbsValue = + elem.locals.getOrElse(x, AbsValue.Bot) + + /** lookup global variables */ + def lookupGlobal(x: Global): AbsValue + + /** existence checks */ + def exists(ref: AbsRefValue): AbsValue = ref match + case AbsRefId(id) => !directLookup(id).isAbsent + case AbsRefProp(base, prop) => + !elem.get(base, prop).isAbsent + + /** define local variables */ + def defineLocal(pairs: (Local, AbsValue)*): Elem + + /** define global variables */ + def defineGlobal(pairs: (Global, AbsValue)*): Elem + + /** setter with reference values */ + def update(refV: AbsRefValue, value: AbsValue): Elem = refV match + case AbsRefId(x) => update(x, value) + case AbsRefProp(base, prop) => update(base, prop, value) + + /** identifier setter */ + def update(x: Id, value: AbsValue): Elem + + /** property setter */ + def update(base: AbsValue, prop: AbsValue, value: AbsValue): Elem + + /** deletion with reference values */ + def delete(refV: AbsRefValue): Elem + + /** push values to a list */ + def push(list: AbsValue, value: AbsValue, front: Boolean): Elem + + /** remove a value in a list */ + def remove(list: AbsValue, value: AbsValue): Elem + + /** pop a value in a list */ + def pop(list: AbsValue, front: Boolean): (AbsValue, Elem) + + /** set a type to an address partition */ + def setType(part: AbsValue, tname: String): (AbsValue, Elem) + + /** copy object */ + def copyObj(to: AllocSite, from: AbsValue): (AbsValue, Elem) + + /** get object keys */ + def keys( + to: AllocSite, + part: AbsValue, + intSorted: Boolean, + ): (AbsValue, Elem) + + /** list concatenation */ + def concat( + to: AllocSite, + ls: Iterable[AbsValue] = Nil, + ): (AbsValue, Elem) + + /** get childeren of AST */ + def getChildren( + to: AllocSite, + ast: AbsValue, + ): (AbsValue, Elem) + + /** get items of AST */ + def getItems( + to: AllocSite, + nt: AbsValue, + ast: AbsValue, + ): (AbsValue, Elem) + + /** allocation of map with address partitions */ + def allocMap( + to: AllocSite, + tname: String, + pairs: Iterable[(AbsValue, AbsValue)], + ): (AbsValue, Elem) + + /** allocation of list with address partitions */ + def allocList( + to: AllocSite, + list: Iterable[AbsValue] = Nil, + ): (AbsValue, Elem) + + /** allocation of symbol with address partitions */ + def allocSymbol(to: AllocSite, desc: AbsValue): (AbsValue, Elem) + + /** check contains */ + def contains(list: AbsValue, value: AbsValue): AbsValue + + /** find merged parts */ + def findMerged: Unit + + /** handle calls */ + def doCall: Elem + def doProcStart(fixed: Set[Part]): Elem + + /** handle returns (elem: return states / to: caller states) */ + def doReturn(to: Elem, defs: (Local, AbsValue)*): Elem = + doReturn(to, defs) + def doReturn(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem + def doProcEnd(to: Elem, defs: (Local, AbsValue)*): Elem + def doProcEnd(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem + def garbageCollected: Elem + + /** get reachable address partitions */ + def reachableParts: Set[Part] + + /** singleton checks */ + def isSingle: Boolean + + /** singleton address partition checks */ + def isSingle(part: Part): Boolean + + /** copy */ + def copied( + locals: Map[Local, AbsValue] = Map(), + ): Elem + + /** conversion to string */ + def getString(detail: Boolean): String + + /** get string with detailed shape of address partitions */ + def getString(value: AbsValue): String + + /** getters */ + def reachable: Boolean + def locals: Map[Local, AbsValue] + def globals: Map[Global, AbsValue] + def heap: AbsHeap + + /** check bottom elements in abstract semantics */ + protected def bottomCheck[A](dom: Domain[A])( + vs: dom.Elem*, + )(f: => Elem): Elem = elem.bottomCheck(dom)(vs)(f) + protected def bottomCheck[A](dom: Domain[_])( + vs: Iterable[dom.Elem], + )(f: => Elem): Elem = + if (elem.isBottom || vs.exists(_.isBottom)) Bot else f + protected def bottomCheck[A](dom: Domain[A])( + vs: dom.Elem*, + )(f: => (AbsValue, Elem)): (AbsValue, Elem) = elem.bottomCheck(dom)(vs)(f) + protected def bottomCheck[A](dom: Domain[A])( + vs: Iterable[dom.Elem], + )(f: => (AbsValue, Elem)): (AbsValue, Elem) = + if (elem.isBottom || vs.exists(_.isBottom)) (AbsValue.Bot, Bot) + else f + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/state/StateTypeDomain.scala b/src/main/scala/esmeta/analyzer/domain/state/StateTypeDomain.scala new file mode 100644 index 0000000000..3ad0495dae --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/state/StateTypeDomain.scala @@ -0,0 +1,414 @@ +package esmeta.analyzer.domain.state + +import esmeta.LINE_SEP +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.ir.{*, given} +import esmeta.es +import esmeta.es.* +import esmeta.ty.* +import esmeta.ty.util.Stringifier.{*, given} +import esmeta.util.* +import esmeta.util.Appender +import esmeta.util.Appender.{*, given} +import esmeta.util.BaseUtils.* + +trait StateTypeDomainDecl { self: Self => + + /** type domain for states */ + object StateTypeDomain extends StateDomain { + + /** elements */ + case class Elem( + reachable: Boolean = false, + locals: Map[Local, AbsValue] = Map(), + ) extends Appendable + + /** top element */ + lazy val Top: Elem = exploded("top abstract state") + + /** set bases */ + def setBase(init: Initialize): Unit = base = for { + (x, (_, t)) <- init.initTypedGlobal.toMap + } yield x -> AbsValue(t) + private var base: Map[Global, AbsValue] = Map() + + /** bottom element */ + val Bot: Elem = Elem() + + /** empty element */ + val Empty: Elem = Elem(reachable = true) + + /** abstraction functions */ + def alpha(xs: Iterable[State]): Elem = Top + + /** appender */ + given rule: Rule[Elem] = mkRule(true) + + /** simpler appender */ + private val shortRule: Rule[Elem] = mkRule(false) + + /** element interfaces */ + extension (elem: Elem) { + + /** bottom check */ + override def isBottom = !elem.reachable + + /** partial order */ + def ⊑(that: Elem): Boolean = (elem, that) match + case _ if elem.isBottom => true + case _ if that.isBottom => false + case (Elem(_, llocals), Elem(_, rlocals)) => + (llocals.keySet ++ rlocals.keySet).forall(x => { + elem.lookupLocal(x) ⊑ that.lookupLocal(x) + }) + + /** join operator */ + def ⊔(that: Elem): Elem = (elem, that) match + case _ if elem.isBottom => that + case _ if that.isBottom => elem + case (l, r) => + val newLocals = (for { + x <- (l.locals.keySet ++ r.locals.keySet).toList + v = elem.lookupLocal(x) ⊔ that.lookupLocal(x) + } yield x -> v).toMap + Elem(true, newLocals) + + /** meet operator */ + override def ⊓(that: Elem): Elem = (elem, that) match + case _ if elem.isBottom || that.isBottom => Bot + case (l, r) => + val newLocals = (for { + x <- (l.locals.keySet ++ r.locals.keySet).toList + v = elem.lookupLocal(x) ⊓ that.lookupLocal(x) + } yield x -> v).toMap + Elem(true, newLocals) + + /** getters with bases and properties */ + def get(base: AbsValue, prop: AbsValue): AbsValue = + val baseTy = base.ty + val propTy = prop.ty + AbsValue( + lookupComp(baseTy.comp, propTy) || + lookupAst(baseTy.astValue, propTy) || + lookupStr(baseTy.str, propTy) || + lookupList(baseTy.list, propTy) || + lookupName(baseTy.name, propTy) || + lookupRecord(baseTy.record, propTy) || + lookupSymbol(baseTy.symbol, propTy) || + lookupSubMap(baseTy.subMap, propTy), + ) + + /** getters with an address partition */ + def get(part: Part): AbsObj = AbsObj.Bot + + /** lookup global variables */ + def lookupGlobal(x: Global): AbsValue = base.getOrElse(x, AbsValue.Bot) + + /** identifier setter */ + def update(x: Id, value: AbsValue): Elem = x match + case x: Local => defineLocal(x -> value) + case x: Global => + // TODO if (value !⊑ base(x)) + // warning(s"invalid global variable update: $x = $value") + elem + + /** property setter */ + def update(base: AbsValue, prop: AbsValue, value: AbsValue): Elem = elem + + /** deletion with reference values */ + def delete(refV: AbsRefValue): Elem = elem + + /** push values to a list */ + def push(list: AbsValue, value: AbsValue, front: Boolean): Elem = elem + + /** remove a value in a list */ + def remove(list: AbsValue, value: AbsValue): Elem = elem + + /** pop a value in a list */ + def pop(list: AbsValue, front: Boolean): (AbsValue, Elem) = + (list.ty.list.elem.fold(AbsValue.Bot)(AbsValue(_)), elem) + + /** set a type to an address partition */ + def setType(v: AbsValue, tname: String): (AbsValue, Elem) = + (AbsValue(NameT(tname)), elem) + + /** copy object */ + def copyObj(to: AllocSite, from: AbsValue): (AbsValue, Elem) = + (from, elem) + + /** get object keys */ + def keys( + to: AllocSite, + v: AbsValue, + intSorted: Boolean, + ): (AbsValue, Elem) = + val value = + if (v.ty.subMap.isBottom) AbsValue.Bot + else AbsValue(ListT(StrT)) + (value, elem) + + /** list concatenation */ + def concat( + to: AllocSite, + lists: Iterable[AbsValue] = Nil, + ): (AbsValue, Elem) = + val value = AbsValue(ListT((for { + list <- lists + elem <- list.ty.list.elem + } yield elem).foldLeft(ValueTy())(_ || _))) + (value, elem) + + /** get childeren of AST */ + def getChildren( + to: AllocSite, + ast: AbsValue, + ): (AbsValue, Elem) = (AbsValue(ListT(AstT)), elem) + + /** get items of AST */ + def getItems( + to: AllocSite, + nt: AbsValue, + ast: AbsValue, + ): (AbsValue, Elem) = nt.ty.nt.getSingle match + case One(Nt(name, _)) => (AbsValue(ListT(AstT(name))), elem) + case Many => exploded(s"imprecise nt name: $nt") + case Zero => (AbsValue.Bot, Bot) + + /** allocation of map with address partitions */ + def allocMap( + to: AllocSite, + tname: String, + pairs: Iterable[(AbsValue, AbsValue)], + ): (AbsValue, Elem) = + val value = + if (tname == "Record") RecordT((for { + (k, v) <- pairs + } yield k.getSingle match + case One(Str(key)) => key -> v.ty + case _ => exploded(s"imprecise field name: $k") + ).toMap) + else NameT(tname) + (AbsValue(value), elem) + + /** allocation of list with address partitions */ + def allocList( + to: AllocSite, + list: Iterable[AbsValue] = Nil, + ): (AbsValue, Elem) = + val listT = ListT(list.foldLeft(ValueTy()) { case (l, r) => l || r.ty }) + (AbsValue(listT), elem) + + /** allocation of symbol with address partitions */ + def allocSymbol(to: AllocSite, desc: AbsValue): (AbsValue, Elem) = + (AbsValue(SymbolT), elem) + + /** check contains */ + def contains(list: AbsValue, value: AbsValue): AbsValue = + if (list.ty.list.isBottom) AbsValue.Bot + else AbsValue.boolTop + + /** define global variables */ + def defineGlobal(pairs: (Global, AbsValue)*): Elem = elem + + /** define local variables */ + def defineLocal(pairs: (Local, AbsValue)*): Elem = + elem.copy(locals = locals ++ pairs) + + /** singleton checks */ + override def isSingle: Boolean = false + + /** singleton address partition checks */ + def isSingle(part: Part): Boolean = false + + /** find merged parts */ + def findMerged: Unit = {} + + /** handle calls */ + def doCall: Elem = elem + def doProcStart(fixed: Set[Part]): Elem = elem + + /** handle returns (elem: return states / to: caller states) */ + def doReturn( + to: Elem, + defs: Iterable[(Local, AbsValue)], + ): Elem = Elem( + reachable = true, + locals = to.locals ++ defs, + ) + + def doProcEnd(to: Elem, defs: (Local, AbsValue)*): Elem = elem + def doProcEnd(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem = elem + + /** garbage collection */ + def garbageCollected: Elem = elem + + /** get reachable address partitions */ + def reachableParts: Set[Part] = Set() + + /** copy */ + def copied(locals: Map[Local, AbsValue] = Map()): Elem = + elem.copy(locals = locals) + + /** get string */ + def getString(detail: Boolean): String = elem.toString + + /** get string with detailed shapes of locations */ + def getString(value: AbsValue): String = value.toString + + /** getters */ + def reachable: Boolean = elem.reachable + def locals: Map[Local, AbsValue] = locals + def globals: Map[Global, AbsValue] = base + def heap: AbsHeap = AbsHeap.Bot + } + + // appender generator + private def mkRule(detail: Boolean): Rule[Elem] = (app, elem) => + if (!elem.isBottom) { + val irStringifier = IRElem.getStringifier(detail, false) + import irStringifier.given + given Rule[Map[Local, AbsValue]] = sortedMapRule(sep = ": ") + app >> elem.locals >> LINE_SEP + } else app >> "⊥" + + // completion record lookup + lazy val constTyForAbruptTarget = + CONSTT_BREAK || CONSTT_CONTINUE || CONSTT_RETURN || CONSTT_THROW + private def lookupComp(comp: CompTy, prop: ValueTy): ValueTy = + val str = prop.str + val normal = !comp.normal.isBottom + val abrupt = !comp.abrupt.isBottom + var res = ValueTy() + if (str contains "Value") + if (normal) res ||= ValueTy(pureValue = comp.normal) + if (abrupt) { + if (comp.abrupt.contains("return") || comp.abrupt.contains("throw")) + res ||= ESValueT + if (comp.abrupt.contains("continue") || comp.abrupt.contains("break")) + res || CONSTT_EMPTY + } + if (str contains "Target") + if (normal) res ||= CONSTT_EMPTY + if (abrupt) res ||= StrT || CONSTT_EMPTY + if (str contains "Type") + if (normal) res ||= CONSTT_NORMAL + if (abrupt) res ||= constTyForAbruptTarget + // TODO if (!comp.isBottom) + // boundCheck( + // prop, + // StrT("Value", "Target", "Type"), + // t => s"invalid access: $t of $comp", + // ) + res + + // AST lookup + private def lookupAst(ast: AstValueTy, prop: ValueTy): ValueTy = ast match + case AstValueTy.Bot => ValueTy.Bot + case AstSingleTy(name, idx, subIdx) => + lookupAstIdxProp(name, idx, subIdx)(prop) || + lookupAstStrProp(prop) + case AstNameTy(names) => + if (!prop.math.isBottom) AstT // TODO more precise + else lookupAstStrProp(prop) + case _ => AstT + // TODO if (!ast.isBottom) + // boundCheck(prop, MathT || StrT, t => s"invalid access: $t of $ast") + + // lookup index properties of ASTs + private def lookupAstIdxProp( + name: String, + idx: Int, + subIdx: Int, + )(prop: ValueTy): ValueTy = prop.math.getSingle match + case One(n) if n.isValidInt => + val propIdx = n.toInt + val rhs = cfg.grammar.nameMap(name).rhsList(idx) + val nts = rhs.getNts(subIdx) + nts(propIdx).fold(AbsentT)(AstT(_)) + case Zero | One(_) => ValueTy.Bot + case _ => AstT // TODO more precise + + // lookup string properties of ASTs + private def lookupAstStrProp(prop: ValueTy): ValueTy = + val nameMap = cfg.grammar.nameMap + prop.str.getSingle match + case Zero => ValueTy.Bot + case One(name) if nameMap contains name => AstT(name) + case _ => AstT // TODO warning(s"invalid access: $name of $ast") + + // string lookup + private def lookupStr(str: BSet[String], prop: ValueTy): ValueTy = + if (str.isBottom) ValueTy.Bot + else { + var res = ValueTy.Bot + if (prop.str contains "length") res ||= MathT + if (!prop.math.isBottom) res ||= CodeUnitT + // TODO if (!str.isBottom) + // boundCheck( + // prop, + // MathT || StrT("length"), + // t => s"invalid access: $t of ${PureValueTy(str = str)}", + // ) + res + } + + // named record lookup + private def lookupName(obj: NameTy, prop: ValueTy): ValueTy = + var res = ValueTy() + val str = prop.str + for { + name <- obj.set + propStr <- str match + case Inf => + if (name == "IntrinsicsRecord") res ||= ObjectT + Set() + case Fin(set) => set + } res ||= cfg.tyModel.getProp(name, propStr) + res + + // record lookup + private def lookupRecord(record: RecordTy, prop: ValueTy): ValueTy = + val str = prop.str + var res = ValueTy() + def add(propStr: String): Unit = record match + case RecordTy.Top => + case RecordTy.Elem(map) => map.get(propStr).map(res ||= _) + if (!record.isBottom) str match + case Inf => + case Fin(set) => + for (propStr <- set) add(propStr) + res + + // list lookup + private def lookupList(list: ListTy, prop: ValueTy): ValueTy = + var res = ValueTy() + val str = prop.str + val math = prop.math + for (ty <- list.elem) + if (str contains "length") res ||= MathT + if (!math.isBottom) res ||= ty + res + + // symbol lookup + private def lookupSymbol(symbol: Boolean, prop: ValueTy): ValueTy = + if (symbol && prop.str.contains("Description")) StrT + else ValueTy() + + // submap lookup + private def lookupSubMap(subMap: SubMapTy, prop: ValueTy): ValueTy = + if (!subMap.isBottom) ValueTy(pureValue = subMap.value) + else ValueTy() + + // bound check + private def boundCheck( + ty: ValueTy, + boundTy: => ValueTy, + f: ValueTy => String, + ): Unit = + // val other = ty -- boundTy + // if (!other.isBottom) warning(f(other)) + () + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/state/TypeDomain.scala b/src/main/scala/esmeta/analyzer/domain/state/TypeDomain.scala deleted file mode 100644 index b97e690071..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/state/TypeDomain.scala +++ /dev/null @@ -1,409 +0,0 @@ -package esmeta.analyzer.domain.state - -import esmeta.LINE_SEP -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.ir.{*, given} -import esmeta.es -import esmeta.es.* -import esmeta.ty.* -import esmeta.ty.util.Stringifier.{*, given} -import esmeta.util.* -import esmeta.util.Appender -import esmeta.util.Appender.{*, given} -import esmeta.util.BaseUtils.* - -/** type domain for states */ -object TypeDomain extends state.Domain { - - /** elements */ - case class Elem( - reachable: Boolean = false, - locals: Map[Local, AbsValue] = Map(), - ) extends Appendable - - /** top element */ - lazy val Top: Elem = exploded("top abstract state") - - /** set bases */ - def setBase(init: Initialize): Unit = base = for { - (x, (_, t)) <- init.initTypedGlobal.toMap - } yield x -> AbsValue(t) - private var base: Map[Global, AbsValue] = Map() - - /** bottom element */ - val Bot: Elem = Elem() - - /** empty element */ - val Empty: Elem = Elem(reachable = true) - - /** abstraction functions */ - def alpha(xs: Iterable[State]): Elem = Top - - /** appender */ - given rule: Rule[Elem] = mkRule(true) - - /** simpler appender */ - private val shortRule: Rule[Elem] = mkRule(false) - - /** element interfaces */ - extension (elem: Elem) { - - /** bottom check */ - override def isBottom = !elem.reachable - - /** partial order */ - def ⊑(that: Elem): Boolean = (elem, that) match - case _ if elem.isBottom => true - case _ if that.isBottom => false - case (Elem(_, llocals), Elem(_, rlocals)) => - (llocals.keySet ++ rlocals.keySet).forall(x => { - elem.lookupLocal(x) ⊑ that.lookupLocal(x) - }) - - /** join operator */ - def ⊔(that: Elem): Elem = (elem, that) match - case _ if elem.isBottom => that - case _ if that.isBottom => elem - case (l, r) => - val newLocals = (for { - x <- (l.locals.keySet ++ r.locals.keySet).toList - v = elem.lookupLocal(x) ⊔ that.lookupLocal(x) - } yield x -> v).toMap - Elem(true, newLocals) - - /** meet operator */ - override def ⊓(that: Elem): Elem = (elem, that) match - case _ if elem.isBottom || that.isBottom => Bot - case (l, r) => - val newLocals = (for { - x <- (l.locals.keySet ++ r.locals.keySet).toList - v = elem.lookupLocal(x) ⊓ that.lookupLocal(x) - } yield x -> v).toMap - Elem(true, newLocals) - - /** getters with bases and properties */ - def get(base: AbsValue, prop: AbsValue): AbsValue = - val baseTy = base.ty - val propTy = prop.ty - AbsValue( - lookupComp(baseTy.comp, propTy) || - lookupAst(baseTy.astValue, propTy) || - lookupStr(baseTy.str, propTy) || - lookupList(baseTy.list, propTy) || - lookupName(baseTy.name, propTy) || - lookupRecord(baseTy.record, propTy) || - lookupSymbol(baseTy.symbol, propTy) || - lookupSubMap(baseTy.subMap, propTy), - ) - - /** getters with an address partition */ - def get(part: Part): AbsObj = AbsObj.Bot - - /** lookup global variables */ - def lookupGlobal(x: Global): AbsValue = base.getOrElse(x, AbsValue.Bot) - - /** identifier setter */ - def update(x: Id, value: AbsValue): Elem = x match - case x: Local => defineLocal(x -> value) - case x: Global => - // TODO if (value !⊑ base(x)) - // warning(s"invalid global variable update: $x = $value") - elem - - /** property setter */ - def update(base: AbsValue, prop: AbsValue, value: AbsValue): Elem = elem - - /** deletion with reference values */ - def delete(refV: AbsRefValue): Elem = elem - - /** push values to a list */ - def push(list: AbsValue, value: AbsValue, front: Boolean): Elem = elem - - /** remove a value in a list */ - def remove(list: AbsValue, value: AbsValue): Elem = elem - - /** pop a value in a list */ - def pop(list: AbsValue, front: Boolean): (AbsValue, Elem) = - (list.ty.list.elem.fold(AbsValue.Bot)(AbsValue(_)), elem) - - /** set a type to an address partition */ - def setType(v: AbsValue, tname: String): (AbsValue, Elem) = - (AbsValue(NameT(tname)), elem) - - /** copy object */ - def copyObj(to: AllocSite, from: AbsValue): (AbsValue, Elem) = (from, elem) - - /** get object keys */ - def keys( - to: AllocSite, - v: AbsValue, - intSorted: Boolean, - ): (AbsValue, Elem) = - val value = - if (v.ty.subMap.isBottom) AbsValue.Bot - else AbsValue(ListT(StrT)) - (value, elem) - - /** list concatenation */ - def concat( - to: AllocSite, - lists: Iterable[AbsValue] = Nil, - ): (AbsValue, Elem) = - val value = AbsValue(ListT((for { - list <- lists - elem <- list.ty.list.elem - } yield elem).foldLeft(ValueTy())(_ || _))) - (value, elem) - - /** get childeren of AST */ - def getChildren( - to: AllocSite, - ast: AbsValue, - ): (AbsValue, Elem) = (AbsValue(ListT(AstT)), elem) - - /** get items of AST */ - def getItems( - to: AllocSite, - nt: AbsValue, - ast: AbsValue, - ): (AbsValue, Elem) = nt.ty.nt.getSingle match - case One(Nt(name, _)) => (AbsValue(ListT(AstT(name))), elem) - case Many => exploded(s"imprecise nt name: $nt") - case Zero => (AbsValue.Bot, Bot) - - /** allocation of map with address partitions */ - def allocMap( - to: AllocSite, - tname: String, - pairs: Iterable[(AbsValue, AbsValue)], - ): (AbsValue, Elem) = - val value = - if (tname == "Record") RecordT((for { - (k, v) <- pairs - } yield k.getSingle match - case One(Str(key)) => key -> v.ty - case _ => exploded(s"imprecise field name: $k") - ).toMap) - else NameT(tname) - (AbsValue(value), elem) - - /** allocation of list with address partitions */ - def allocList( - to: AllocSite, - list: Iterable[AbsValue] = Nil, - ): (AbsValue, Elem) = - val listT = ListT(list.foldLeft(ValueTy()) { case (l, r) => l || r.ty }) - (AbsValue(listT), elem) - - /** allocation of symbol with address partitions */ - def allocSymbol(to: AllocSite, desc: AbsValue): (AbsValue, Elem) = - (AbsValue(SymbolT), elem) - - /** check contains */ - def contains(list: AbsValue, value: AbsValue): AbsValue = - if (list.ty.list.isBottom) AbsValue.Bot - else AbsValue.boolTop - - /** define global variables */ - def defineGlobal(pairs: (Global, AbsValue)*): Elem = elem - - /** define local variables */ - def defineLocal(pairs: (Local, AbsValue)*): Elem = - elem.copy(locals = locals ++ pairs) - - /** singleton checks */ - override def isSingle: Boolean = false - - /** singleton address partition checks */ - def isSingle(part: Part): Boolean = false - - /** find merged parts */ - def findMerged: Unit = {} - - /** handle calls */ - def doCall: Elem = elem - def doProcStart(fixed: Set[Part]): Elem = elem - - /** handle returns (elem: return states / to: caller states) */ - def doReturn( - to: Elem, - defs: Iterable[(Local, AbsValue)], - ): Elem = Elem( - reachable = true, - locals = to.locals ++ defs, - ) - - def doProcEnd(to: Elem, defs: (Local, AbsValue)*): Elem = elem - def doProcEnd(to: Elem, defs: Iterable[(Local, AbsValue)]): Elem = elem - - /** garbage collection */ - def garbageCollected: Elem = elem - - /** get reachable address partitions */ - def reachableParts: Set[Part] = Set() - - /** copy */ - def copied(locals: Map[Local, AbsValue] = Map()): Elem = - elem.copy(locals = locals) - - /** get string */ - def getString(detail: Boolean): String = elem.toString - - /** get string with detailed shapes of locations */ - def getString(value: AbsValue): String = value.toString - - /** getters */ - def reachable: Boolean = elem.reachable - def locals: Map[Local, AbsValue] = locals - def globals: Map[Global, AbsValue] = base - def heap: AbsHeap = AbsHeap.Bot - } - - // appender generator - private def mkRule(detail: Boolean): Rule[Elem] = (app, elem) => - if (!elem.isBottom) { - val irStringifier = IRElem.getStringifier(detail, false) - import irStringifier.given - given Rule[Map[Local, AbsValue]] = sortedMapRule(sep = ": ") - app >> elem.locals >> LINE_SEP - } else app >> "⊥" - - // completion record lookup - lazy val constTyForAbruptTarget = - CONSTT_BREAK || CONSTT_CONTINUE || CONSTT_RETURN || CONSTT_THROW - private def lookupComp(comp: CompTy, prop: ValueTy): ValueTy = - val str = prop.str - val normal = !comp.normal.isBottom - val abrupt = !comp.abrupt.isBottom - var res = ValueTy() - if (str contains "Value") - if (normal) res ||= ValueTy(pureValue = comp.normal) - if (abrupt) { - if (comp.abrupt.contains("return") || comp.abrupt.contains("throw")) - res ||= ESValueT - if (comp.abrupt.contains("continue") || comp.abrupt.contains("break")) - res || CONSTT_EMPTY - } - if (str contains "Target") - if (normal) res ||= CONSTT_EMPTY - if (abrupt) res ||= StrT || CONSTT_EMPTY - if (str contains "Type") - if (normal) res ||= CONSTT_NORMAL - if (abrupt) res ||= constTyForAbruptTarget - // TODO if (!comp.isBottom) - // boundCheck( - // prop, - // StrT("Value", "Target", "Type"), - // t => s"invalid access: $t of $comp", - // ) - res - - // AST lookup - private def lookupAst(ast: AstValueTy, prop: ValueTy): ValueTy = ast match - case AstValueTy.Bot => ValueTy.Bot - case AstSingleTy(name, idx, subIdx) => - lookupAstIdxProp(name, idx, subIdx)(prop) || - lookupAstStrProp(prop) - case AstNameTy(names) => - if (!prop.math.isBottom) AstT // TODO more precise - else lookupAstStrProp(prop) - case _ => AstT - // TODO if (!ast.isBottom) - // boundCheck(prop, MathT || StrT, t => s"invalid access: $t of $ast") - - // lookup index properties of ASTs - private def lookupAstIdxProp( - name: String, - idx: Int, - subIdx: Int, - )(prop: ValueTy): ValueTy = prop.math.getSingle match - case One(n) if n.isValidInt => - val propIdx = n.toInt - val rhs = cfg.grammar.nameMap(name).rhsList(idx) - val nts = rhs.getNts(subIdx) - nts(propIdx).fold(AbsentT)(AstT(_)) - case Zero | One(_) => ValueTy.Bot - case _ => AstT // TODO more precise - - // lookup string properties of ASTs - private def lookupAstStrProp(prop: ValueTy): ValueTy = - val nameMap = cfg.grammar.nameMap - prop.str.getSingle match - case Zero => ValueTy.Bot - case One(name) if nameMap contains name => AstT(name) - case _ => AstT // TODO warning(s"invalid access: $name of $ast") - - // string lookup - private def lookupStr(str: BSet[String], prop: ValueTy): ValueTy = - if (str.isBottom) ValueTy.Bot - else { - var res = ValueTy.Bot - if (prop.str contains "length") res ||= MathT - if (!prop.math.isBottom) res ||= CodeUnitT - // TODO if (!str.isBottom) - // boundCheck( - // prop, - // MathT || StrT("length"), - // t => s"invalid access: $t of ${PureValueTy(str = str)}", - // ) - res - } - - // named record lookup - private def lookupName(obj: NameTy, prop: ValueTy): ValueTy = - var res = ValueTy() - val str = prop.str - for { - name <- obj.set - propStr <- str match - case Inf => - if (name == "IntrinsicsRecord") res ||= ObjectT - Set() - case Fin(set) => set - } res ||= cfg.tyModel.getProp(name, propStr) - res - - // record lookup - private def lookupRecord(record: RecordTy, prop: ValueTy): ValueTy = - val str = prop.str - var res = ValueTy() - def add(propStr: String): Unit = record match - case RecordTy.Top => - case RecordTy.Elem(map) => map.get(propStr).map(res ||= _) - if (!record.isBottom) str match - case Inf => - case Fin(set) => - for (propStr <- set) add(propStr) - res - - // list lookup - private def lookupList(list: ListTy, prop: ValueTy): ValueTy = - var res = ValueTy() - val str = prop.str - val math = prop.math - for (ty <- list.elem) - if (str contains "length") res ||= MathT - if (!math.isBottom) res ||= ty - res - - // symbol lookup - private def lookupSymbol(symbol: Boolean, prop: ValueTy): ValueTy = - if (symbol && prop.str.contains("Description")) StrT - else ValueTy() - - // submap lookup - private def lookupSubMap(subMap: SubMapTy, prop: ValueTy): ValueTy = - if (!subMap.isBottom) ValueTy(pureValue = subMap.value) - else ValueTy() - - // bound check - private def boundCheck( - ty: ValueTy, - boundTy: => ValueTy, - f: ValueTy => String, - ): Unit = - val other = ty -- boundTy - if (!other.isBottom) warning(f(other)) -} diff --git a/src/main/scala/esmeta/analyzer/domain/state/package.scala b/src/main/scala/esmeta/analyzer/domain/state/package.scala new file mode 100644 index 0000000000..0617a88586 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/state/package.scala @@ -0,0 +1,12 @@ +package esmeta.analyzer.domain.state + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends StateDomainDecl + with StateBasicDomainDecl + with StateTypeDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/str/Domain.scala b/src/main/scala/esmeta/analyzer/domain/str/Domain.scala deleted file mode 100644 index 4d1e53473a..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/str/Domain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.str - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** abstract string domain */ -trait Domain extends domain.Domain[Str] diff --git a/src/main/scala/esmeta/analyzer/domain/str/SetDomain.scala b/src/main/scala/esmeta/analyzer/domain/str/SetDomain.scala deleted file mode 100644 index a2e7c2a4f3..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/str/SetDomain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package esmeta.analyzer.domain.str - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** set domain for string values */ -class SetDomain( - maxSizeOpt: Option[Int] = None, // max size of set -) extends str.Domain - with domain.SetDomain[Str]("str", maxSizeOpt) diff --git a/src/main/scala/esmeta/analyzer/domain/str/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/str/SimpleDomain.scala deleted file mode 100644 index 17b75ac36e..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/str/SimpleDomain.scala +++ /dev/null @@ -1,8 +0,0 @@ -package esmeta.analyzer.domain.str - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* - -/** simple domain for string values */ -object SimpleDomain extends str.Domain with SimpleDomain[Str]("str") diff --git a/src/main/scala/esmeta/analyzer/domain/str/FlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/str/StrDomain.scala similarity index 52% rename from src/main/scala/esmeta/analyzer/domain/str/FlatDomain.scala rename to src/main/scala/esmeta/analyzer/domain/str/StrDomain.scala index 4216aa7665..6e94c216cd 100644 --- a/src/main/scala/esmeta/analyzer/domain/str/FlatDomain.scala +++ b/src/main/scala/esmeta/analyzer/domain/str/StrDomain.scala @@ -4,5 +4,8 @@ import esmeta.analyzer.* import esmeta.analyzer.domain.* import esmeta.state.* -/** flat domain for string values */ -object FlatDomain extends str.Domain with FlatDomain[Str]("str") +trait StrDomainDecl { self: Self => + + /** abstract string domain */ + trait StrDomain extends Domain[Str] +} diff --git a/src/main/scala/esmeta/analyzer/domain/str/StrFlatDomain.scala b/src/main/scala/esmeta/analyzer/domain/str/StrFlatDomain.scala new file mode 100644 index 0000000000..81c52cb8d6 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/str/StrFlatDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.str + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait StrFlatDomainDecl { self: Self => + + /** flat domain for string values */ + object StrFlatDomain extends StrDomain with FlatDomain[Str]("str") +} diff --git a/src/main/scala/esmeta/analyzer/domain/str/StrSetDomain.scala b/src/main/scala/esmeta/analyzer/domain/str/StrSetDomain.scala new file mode 100644 index 0000000000..32246988c8 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/str/StrSetDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.str + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait StrSetDomainDecl { self: Self => + + /** set domain for string values */ + class StrSetDomain( + maxSizeOpt: Option[Int] = None, // max size of set + ) extends StrDomain + with SetDomain[Str]("str", maxSizeOpt) +} diff --git a/src/main/scala/esmeta/analyzer/domain/str/StrSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/str/StrSimpleDomain.scala new file mode 100644 index 0000000000..8558feb480 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/str/StrSimpleDomain.scala @@ -0,0 +1,11 @@ +package esmeta.analyzer.domain.str + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* + +trait StrSimpleDomainDecl { self: Self => + + /** simple domain for string values */ + object StrSimpleDomain extends StrDomain with SimpleDomain[Str]("str") +} diff --git a/src/main/scala/esmeta/analyzer/domain/str/package.scala b/src/main/scala/esmeta/analyzer/domain/str/package.scala new file mode 100644 index 0000000000..9fda6e6be3 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/str/package.scala @@ -0,0 +1,13 @@ +package esmeta.analyzer.domain.str + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends StrDomainDecl + with StrSimpleDomainDecl + with StrFlatDomainDecl + with StrSetDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/domain/undef/SimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/undef/SimpleDomain.scala deleted file mode 100644 index b019bc3b1c..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/undef/SimpleDomain.scala +++ /dev/null @@ -1,9 +0,0 @@ -package esmeta.analyzer.domain.undef - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.state.* -import esmeta.util.* - -/** simple domain for undefined values */ -object SimpleDomain extends undef.Domain with SimpleDomain("undef", Fin(Undef)) diff --git a/src/main/scala/esmeta/analyzer/domain/undef/Domain.scala b/src/main/scala/esmeta/analyzer/domain/undef/UndefDomain.scala similarity index 50% rename from src/main/scala/esmeta/analyzer/domain/undef/Domain.scala rename to src/main/scala/esmeta/analyzer/domain/undef/UndefDomain.scala index 3c69a95420..fdfb0bb499 100644 --- a/src/main/scala/esmeta/analyzer/domain/undef/Domain.scala +++ b/src/main/scala/esmeta/analyzer/domain/undef/UndefDomain.scala @@ -4,5 +4,8 @@ import esmeta.analyzer.* import esmeta.analyzer.domain.* import esmeta.state.* -/** abstract undefined domain */ -trait Domain extends domain.Domain[Undef] +trait UndefDomainDecl { self: Self => + + /** abstract undefined domain */ + trait UndefDomain extends Domain[Undef] +} diff --git a/src/main/scala/esmeta/analyzer/domain/undef/UndefSimpleDomain.scala b/src/main/scala/esmeta/analyzer/domain/undef/UndefSimpleDomain.scala new file mode 100644 index 0000000000..1820f3cf51 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/undef/UndefSimpleDomain.scala @@ -0,0 +1,14 @@ +package esmeta.analyzer.domain.undef + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.state.* +import esmeta.util.* + +trait UndefSimpleDomainDecl { self: Self => + + /** simple domain for undefined values */ + object UndefSimpleDomain + extends UndefDomain + with SimpleDomain("undef", Fin(Undef)) +} diff --git a/src/main/scala/esmeta/analyzer/domain/undef/package.scala b/src/main/scala/esmeta/analyzer/domain/undef/package.scala new file mode 100644 index 0000000000..fd54d6b644 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/undef/package.scala @@ -0,0 +1,7 @@ +package esmeta.analyzer.domain.undef + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl extends UndefDomainDecl with UndefSimpleDomainDecl { self: Self => } diff --git a/src/main/scala/esmeta/analyzer/domain/value/BasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/value/BasicDomain.scala deleted file mode 100644 index 616072e3a1..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/value/BasicDomain.scala +++ /dev/null @@ -1,534 +0,0 @@ -package esmeta.analyzer.domain.value - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.cfg.Func -import esmeta.es.* -import esmeta.state.* -import esmeta.ty.* -import esmeta.ir.{COp, Name, VOp, MOp} -import esmeta.parser.ESValueParser -import esmeta.util.* -import esmeta.util.Appender.* - -/** basic domain for values */ -object BasicDomain extends value.Domain { - - /** elements */ - case class Elem( - comp: AbsComp = AbsComp.Bot, - pureValue: AbsPureValue = AbsPureValue.Bot, - ) extends Appendable - - /** top element */ - lazy val Top: Elem = exploded("top abstract value") - - /** bottom element */ - val Bot: Elem = Elem() - - /** abstraction functions */ - def alpha(xs: Iterable[AValue]): Elem = Elem( - AbsComp(xs.collect { case x: AComp => x }), - AbsPureValue(xs.collect { case x: APureValue => x }), - ) - - /** constructor with types */ - def apply(ty: Ty): Elem = Top - - /** constructor for completions */ - def createCompletion( - ty: AbsValue, - value: AbsValue, - target: AbsValue, - ): Elem = Elem(AbsComp(ty, value, target)) - - /** predefined top values */ - def compTop: Elem = Bot.copy(comp = AbsComp.Top) - def pureValueTop: Elem = Bot.copy(pureValue = AbsPureValue.Top) - val cloTop: Elem = Bot.copy(pureValue = AbsPureValue.cloTop) - val contTop: Elem = Bot.copy(pureValue = AbsPureValue.contTop) - val partTop: Elem = Bot.copy(pureValue = AbsPureValue.partTop) - val astValueTop: Elem = Bot.copy(pureValue = AbsPureValue.astValueTop) - val ntTop: Elem = Bot.copy(pureValue = AbsPureValue.ntTop) - val codeUnitTop: Elem = Bot.copy(pureValue = AbsPureValue.codeUnitTop) - val constTop: Elem = Bot.copy(pureValue = AbsPureValue.constTop) - val mathTop: Elem = Bot.copy(pureValue = AbsPureValue.mathTop) - val simpleValueTop: Elem = Bot.copy(pureValue = AbsPureValue.simpleValueTop) - val numberTop: Elem = Bot.copy(pureValue = AbsPureValue.numberTop) - val bigIntTop: Elem = Bot.copy(pureValue = AbsPureValue.bigIntTop) - val strTop: Elem = Bot.copy(pureValue = AbsPureValue.strTop) - val boolTop: Elem = Bot.copy(pureValue = AbsPureValue.boolTop) - val undefTop: Elem = Bot.copy(pureValue = AbsPureValue.undefTop) - val nullTop: Elem = Bot.copy(pureValue = AbsPureValue.nullTop) - val absentTop: Elem = Bot.copy(pureValue = AbsPureValue.absentTop) - - /** constructors */ - def apply( - comp: AbsComp = AbsComp.Bot, - pureValue: AbsPureValue = AbsPureValue.Bot, - clo: AbsClo = AbsClo.Bot, - cont: AbsCont = AbsCont.Bot, - part: AbsPart = AbsPart.Bot, - astValue: AbsAstValue = AbsAstValue.Bot, - nt: AbsNt = AbsNt.Bot, - codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, - const: AbsConst = AbsConst.Bot, - math: AbsMath = AbsMath.Bot, - simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, - num: AbsNumber = AbsNumber.Bot, - bigInt: AbsBigInt = AbsBigInt.Bot, - str: AbsStr = AbsStr.Bot, - bool: AbsBool = AbsBool.Bot, - undef: AbsUndef = AbsUndef.Bot, - nullv: AbsNull = AbsNull.Bot, - absent: AbsAbsent = AbsAbsent.Bot, - ): Elem = Elem( - comp, - pureValue ⊔ AbsPureValue( - clo, - cont, - part, - astValue, - nt, - codeUnit, - const, - math, - simpleValue ⊔ AbsSimpleValue( - num, - bigInt, - str, - bool, - undef, - nullv, - absent, - ), - ), - ) - - /** extractors */ - def unapply(elem: Elem): Option[RawTuple] = Some( - ( - elem.comp, - elem.pureValue, - ), - ) - - /** appender */ - given rule: Rule[Elem] = (app, elem) => - if (!elem.isBottom) { - val Elem(comp, pureValue) = elem - var strs = Vector[String]() - if (!comp.isBottom) strs :+= comp.toString - if (!pureValue.isBottom) strs :+= pureValue.toString - app >> strs.mkString(", ") - } else app >> "⊥" - - /** transfer for variadic operation */ - def vopTransfer(vop: VOp, vs: List[Elem]): Elem = - import VOp.* - // helpers - def asMath(av: Elem): Option[BigDecimal] = av.getSingle match - case Many => exploded("vop transfer") - case One(Math(n)) => Some(n) - case _ => None - def asStr(av: Elem): Option[String] = av.getSingle match - case Many => exploded("vop transfer") - case One(Str(s)) => Some(s) - case One(CodeUnit(cu)) => Some(cu.toString) - case _ => None - // transfer body - if (!vs.exists(_.isBottom)) vop match - case Min => - val set = scala.collection.mutable.Set[Elem]() - if (vs.exists(apply(NEG_INF) ⊑ _)) set += apply(NEG_INF) - val filtered = vs.filter((v) => !(apply(POS_INF) ⊑ v)) - if (filtered.isEmpty) set += apply(POS_INF) - set += doVopTransfer(asMath, _ min _, apply, filtered) - set.foldLeft(Bot)(_ ⊔ _) - case Max => - val set = scala.collection.mutable.Set[Elem]() - if (vs.exists(apply(POS_INF) ⊑ _)) set += apply(POS_INF) - val filtered = vs.filter((v) => !(apply(NEG_INF) ⊑ v)) - if (filtered.isEmpty) set += apply(NEG_INF) - set += doVopTransfer(asMath, _ min _, apply, filtered) - set.foldLeft(Bot)(_ ⊔ _) - case Concat => doVopTransfer[String](asStr, _ + _, apply, vs) - else Bot - - /** helpers for make transition for variadic operators */ - protected def doVopTransfer[T]( - f: Elem => Option[T], - op: (T, T) => T, - g: T => Elem, - vs: List[Elem], - ): Elem = - val vst = vs.map(f).flatten - if (vst.size != vs.size) Bot - else g(vst.reduce(op)) - - /** transfer for mathematical operation */ - def mopTransfer(mop: MOp, vs: List[Elem]): Elem = mathTop - - /** element interfaces */ - extension (elem: Elem) { - - /** get key values */ - def keyValue: Elem = apply(part = part, str = str) - - /** partial order */ - def ⊑(that: Elem): Boolean = - elem.comp ⊑ that.comp && - elem.pureValue ⊑ that.pureValue - - /** join operator */ - def ⊔(that: Elem): Elem = Elem( - elem.comp ⊔ that.comp, - elem.pureValue ⊔ that.pureValue, - ) - - /** meet operator */ - override def ⊓(that: Elem): Elem = Elem( - elem.comp ⊓ that.comp, - elem.pureValue ⊓ that.pureValue, - ) - - /** prune operator */ - override def --(that: Elem): Elem = Elem( - elem.comp -- that.comp, - elem.pureValue -- that.pureValue, - ) - - /** concretization function */ - override def gamma: BSet[AValue] = - elem.comp.gamma ⊔ - elem.pureValue.gamma - - /** get single value */ - override def getSingle: Flat[AValue] = - elem.comp.getSingle ⊔ - elem.pureValue.getSingle - - /** get reachable address partitions */ - def reachableParts: Set[Part] = - comp.reachableParts ++ pureValue.reachableParts - - /** bitwise operations */ - def &(that: Elem): Elem = ??? // TODO - def |(that: Elem): Elem = ??? // TODO - def ^(that: Elem): Elem = ??? // TODO - - /** comparison operations */ - def =^=(that: Elem): Elem = - apply(bool = (elem.getSingle, that.getSingle) match - case (Zero, _) | (_, Zero) => AbsBool.Bot - case (One(l), One(r)) => AbsBool(Bool(l == r)) - case _ => if ((elem ⊓ that).isBottom) AF else AB, - ) - def ==^==(that: Elem): Elem = ??? // TODO - def <(that: Elem): Elem = ??? // TODO - - /** logical operations */ - def &&(that: Elem): Elem = ??? // TODO - def ||(that: Elem): Elem = ??? // TODO - def ^^(that: Elem): Elem = ??? // TODO - - /** numeric operations */ - def +(that: Elem): Elem = ??? // TODO - // AbsValue( - // str = ( - // (left.str plus right.str) ⊔ - // (left.str plusNum right.num) - // ), - // num = ( - // (left.num plus right.num) ⊔ - // (right.num plusInt left.int) ⊔ - // (left.num plusInt right.int) - // ), - // int = left.int plus right.int, - // bigInt = left.bigInt plus right.bigInt, - // ) - def sub(that: Elem): Elem = ??? // TODO - def /(that: Elem): Elem = ??? // TODO - def *(that: Elem): Elem = ??? // TODO - // AbsValue( - // num = ( - // (left.num mul right.num) ⊔ - // (right.num mulInt left.int) ⊔ - // (left.num mulInt right.int) - // ), - // int = left.int mul right.int, - // bigInt = left.bigInt mul right.bigInt, - // ) - def %(that: Elem): Elem = ??? // TODO - def %%(that: Elem): Elem = ??? // TODO - def **(that: Elem): Elem = ??? // TODO - def <<(that: Elem): Elem = ??? // TODO - def >>>(that: Elem): Elem = ??? // TODO - def >>(that: Elem): Elem = ??? // TODO - - /** unary operations */ - def unary_- : Elem = ??? // TODO - def unary_! : Elem = apply(bool = !elem.bool) - def unary_~ : Elem = ??? // TODO - def abs: Elem = ??? // TODO - def floor: Elem = ??? // TODO - - /** type operations */ - def typeOf(st: AbsState): Elem = - var set = Set[String]() - if (!elem.number.isBottom) set += "Number" - if (!elem.bigInt.isBottom) set += "BigInt" - if (!elem.str.isBottom) set += "String" - if (!elem.bool.isBottom) set += "Boolean" - if (!elem.undef.isBottom) set += "Undefined" - if (!elem.nullv.isBottom) set += "Null" - if (!elem.part.isBottom) for (part <- elem.part) { - val tname = st.get(part).getTy match - case tname if cfg.tyModel.isSubTy(tname, "Object") => "Object" - case tname => tname - set += tname - } - apply(str = AbsStr(set.map(Str.apply))) - - /** type check */ - def typeCheck(tname: String, st: AbsState): Elem = - var bv: AbsBool = AbsBool.Bot - if (!elem.number.isBottom) bv ⊔= AbsBool(Bool(tname == "Number")) - if (!elem.bigInt.isBottom) bv ⊔= AbsBool(Bool(tname == "BigInt")) - if (!elem.str.isBottom) bv ⊔= AbsBool(Bool(tname == "String")) - if (!elem.bool.isBottom) bv ⊔= AbsBool(Bool(tname == "Boolean")) - if (!elem.const.isBottom) - bv ⊔= AbsBool(Bool(tname == "Constant")) - if (!elem.comp.isBottom) - bv ⊔= AbsBool(Bool(tname == "CompletionRecord")) - if (!elem.undef.isBottom) - bv ⊔= AbsBool(Bool(tname == "Undefined")) - if (!elem.nullv.isBottom) bv ⊔= AbsBool(Bool(tname == "Null")) - if (!elem.clo.isBottom) - bv ⊔= AbsBool(Bool(tname == "AbstractClosure")) - elem.astValue.getSingle match - case Zero => /* do nothing */ - case Many => bv = AB - case One(AstValue(ast)) => - bv ⊔= AbsBool( - Bool(tname == "ParseNode" || (ast.types contains tname)), - ) - for (part <- elem.part) { - val newTName = st.get(part).getTy - bv ⊔= AbsBool( - Bool( - newTName == tname || cfg.tyModel.isSubTy(newTName, tname), - ), - ) - } - apply(bool = bv) - - /** helper functions for abstract transfer */ - def convertTo(cop: COp, radix: Elem): Elem = - import COp.* - var newV = Bot - for (CodeUnit(cu) <- elem.codeUnit) newV ⊔= (cop match - case ToMath => apply(Math(cu.toInt)) - case _ => Bot - ) - for (Math(n) <- elem.math) newV ⊔= (cop match - case ToApproxNumber => apply(Number(n.toDouble)) - case ToNumber => apply(Number(n.toDouble)) - case ToBigInt => apply(BigInt(n.toBigInt)) - case ToMath => apply(Math(n)) - case _ => Bot - ) - for (Str(s) <- elem.str) newV ⊔= (cop match - case ToNumber => apply(Number(ESValueParser.str2Number(s))) - case ToBigInt => apply(ESValueParser.str2bigInt(s)) - case _: ToStr => apply(Str(s)) - case _ => Bot - ) - for (Number(d) <- elem.number) newV ⊔= (cop match - case ToMath => apply(Math(d)) - case _: ToStr => - radix.asInt.foldLeft(Bot)((v, n) => v ⊔ apply(toStringHelper(d, n))) - case ToNumber => apply(Number(d)) - case ToBigInt => apply(BigInt(BigDecimal.exact(d).toBigInt)) - case _ => Bot - ) - for (BigInt(b) <- elem.bigInt) newV ⊔= (cop match - case ToMath => apply(Math(b)) - case _: ToStr => - radix.asInt.foldLeft(Bot)((v, n) => v ⊔ apply(Str(b.toString(n)))) - case ToBigInt => apply(BigInt(b)) - case _ => Bot - ) - newV - def sourceText: Elem = apply(str = - AbsStr( - elem.astValue.toList.map(x => - Str(x.ast.toString(grammar = Some(cfg.grammar)).trim), - ), - ), - ) - def parse(rule: Elem): Elem = - var newV: Elem = Bot - // codes - var codes: Set[(String, List[Boolean])] = Set() - for (Str(s) <- elem.str) codes += (s, List()) - for (AstValue(ast) <- elem.astValue) { - val code = ast.toString(grammar = Some(cfg.grammar)) - val args = ast match - case syn: Syntactic => syn.args - case _ => List() - codes += (code, args) - } - // parse - for { - Nt(name, params) <- rule.nt - (str, args) <- codes - parseArgs = if (params.isEmpty) args else params - } newV ⊔= apply(cfg.esParser(name, parseArgs).from(str)) - // result - newV - def duplicated(st: AbsState): Elem = - apply(bool = elem.part.foldLeft(AbsBool.Bot: AbsBool) { - case (avb, part) => avb ⊔ st.get(part).duplicated - }) - def substring(from: Elem): Elem = - (elem.getSingle, from.getSingle) match - case (Zero, _) | (_, Zero) => Bot - case (Many, _) | (_, Many) => exploded("ESubstring") - case (One(Str(s)), One(Math(f))) if f.isValidInt => - apply(s.substring(f.toInt)) - case _ => Bot - - def substring(from: Elem, to: Elem): Elem = - (elem.getSingle, from.getSingle, to.getSingle) match - case (Zero, _, _) | (_, Zero, _) | (_, _, Zero) => Bot - case (Many, _, _) | (_, Many, _) | (_, _, Many) => - exploded("ESubstring") - case ( - One(Str(s)), - One(Math(f)), - One(Math(t)), - ) if f.isValidInt => - if (s.length < t) apply(s.substring(f.toInt)) - else if (t.isValidInt) apply(s.substring(f.toInt, t.toInt)) - else Bot - case _ => Bot - def trim(leading: Boolean, trailing: Boolean): Elem = elem.getSingle match - case Many => exploded("ETrim") - case One(Str(s)) => - apply( - if (leading && trailing) s.trim - else if (leading) s.replaceAll("^\\s+", "") - else if (trailing) s.replaceAll("\\s+$", "") - else s, - ) - case _ => Bot - def clamp(lower: Elem, upper: Elem): Elem = - (elem.getSingle, lower.getSingle, upper.getSingle) match - case (Zero, _, _) | (_, Zero, _) | (_, _, Zero) => Bot - case (Many, _, _) | (_, Many, _) | (_, _, Many) => exploded("EClamp") - case ( - One(target), - One(Math(l)), - One(Math(u)), - ) => - target match - case Math(t) => - apply( - if (t < l) Math(l) - else if (t > u) Math(u) - else Math(t), - ) - case POS_INF => apply(Math(u)) - case NEG_INF => apply(Math(l)) - case _ => Bot - case _ => Bot - def isArrayIndex: Elem = elem.getSingle match - case Zero => Bot - case One(Str(s)) => - val d = ESValueParser.str2Number(s) - val ds = toStringHelper(d) - val UPPER = (1L << 32) - 1 - val l = d.toLong - apply(ds == s && 0 <= l && d == l && l < UPPER) - case One(_) => apply(F) - case Many => exploded("EIsArrayIndex") - - /** prune abstract values */ - def pruneValue(r: Elem, positive: Boolean): Elem = elem - def pruneField(field: String, r: Elem, positive: Boolean): Elem = elem - def pruneType(r: Elem, positive: Boolean): Elem = elem - def pruneTypeCheck(r: Elem, positive: Boolean): Elem = elem - - /** completion helpers */ - def wrapCompletion: Elem = wrapCompletion("normal") - def wrapCompletion(ty: String): Elem = apply(comp = { - if (!pureValue.isBottom) - comp ⊔ AbsComp( - Map(ty -> AbsComp.Result(pureValue, AbsPureValue(CONST_EMPTY))), - ) - else comp - }) - def unwrapCompletion: Elem = - Elem(pureValue = comp.normal.value ⊔ elem.pureValue) - def isCompletion: Elem = - var b: AbsBool = AbsBool.Bot - if (!comp.isBottom) b ⊔= AT - if (!pureValue.isBottom) b ⊔= AF - apply(bool = b) - def normalCompletion: Elem = - if (pureValue.isBottom) Bot - else - val res = AbsComp.Result(pureValue, AbsPureValue(CONST_EMPTY)) - Elem(comp = AbsComp(Map("normal" -> res))) - def abruptCompletion: Elem = apply(comp = comp.removeNormal) - - /** absent helpers */ - def removeAbsent: Elem = elem -- absentTop - def isAbsent: Elem = - var b: AbsBool = AbsBool.Bot - if (!absent.isBottom) b ⊔= AT - if (!removeAbsent.isBottom) b ⊔= AF - apply(bool = b) - - /** refine receiver object */ - def refineThis(func: Func): Elem = elem - - /** get syntactic SDO */ - def getSDO(method: String): List[(Func, Elem)] = ??? - - /** get lexical result */ - def getLexical(method: String): Elem = ??? - - /** getters */ - def comp: AbsComp = elem.comp - def pureValue: AbsPureValue = elem.pureValue - def clo: AbsClo = elem.pureValue.clo - def cont: AbsCont = elem.pureValue.cont - def part: AbsPart = elem.pureValue.part - def astValue: AbsAstValue = elem.pureValue.astValue - def nt: AbsNt = elem.pureValue.nt - def codeUnit: AbsCodeUnit = elem.pureValue.codeUnit - def const: AbsConst = elem.pureValue.const - def math: AbsMath = elem.pureValue.math - def simpleValue: AbsSimpleValue = elem.pureValue.simpleValue - def number: AbsNumber = elem.pureValue.number - def bigInt: AbsBigInt = elem.pureValue.bigInt - def str: AbsStr = elem.pureValue.str - def bool: AbsBool = elem.pureValue.bool - def undef: AbsUndef = elem.pureValue.undef - def nullv: AbsNull = elem.pureValue.nullv - def absent: AbsAbsent = elem.pureValue.absent - def ty: ValueTy = notSupported("value.BasicDomain.toTy") - - // ------------------------------------------------------------------------- - // private helpers - // ------------------------------------------------------------------------- - // conversion to integers - private def asInt: Set[Int] = - var set: Set[Int] = Set() - for (Math(n) <- elem.math if n.isValidInt) set += n.toInt - for (Number(n) <- elem.number if n.isValidInt) set += n.toInt - set - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/value/Domain.scala b/src/main/scala/esmeta/analyzer/domain/value/Domain.scala deleted file mode 100644 index 707fd8b142..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/value/Domain.scala +++ /dev/null @@ -1,202 +0,0 @@ -package esmeta.analyzer.domain.value - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.cfg.Func -import esmeta.es.* -import esmeta.state.* -import esmeta.ty.* -import esmeta.ir.{COp, Name, VOp, MOp} -import esmeta.util.* - -/** abstract valude domain */ -trait Domain extends domain.Domain[AValue] { - - /** abstraction functions for an original value */ - def apply(value: Value): Elem = alpha(AValue.from(value)) - - /** constructor with types */ - def apply(ty: Ty): Elem - - /** constructor for completions */ - def createCompletion( - ty: AbsValue, - value: AbsValue, - target: AbsValue, - ): Elem - - /** abstraction functions for raw data */ - def apply(ast: Ast): Elem = apply(AstValue(ast)) - def apply(n: Double): Elem = apply(Number(n)) - def apply(s: String): Elem = apply(Str(s)) - def apply(b: Boolean): Elem = apply(Bool(b)) - def apply(d: BigDecimal): Elem = apply(Math(d)) - - /** predefined top values */ - def compTop: Elem - def pureValueTop: Elem - def cloTop: Elem - def contTop: Elem - def partTop: Elem - def astValueTop: Elem - def ntTop: Elem - def codeUnitTop: Elem - def constTop: Elem - def mathTop: Elem - def simpleValueTop: Elem - def numberTop: Elem - def bigIntTop: Elem - def strTop: Elem - def boolTop: Elem - def undefTop: Elem - def nullTop: Elem - def absentTop: Elem - - /** constructors */ - def apply( - comp: AbsComp = AbsComp.Bot, - pureValue: AbsPureValue = AbsPureValue.Bot, - clo: AbsClo = AbsClo.Bot, - cont: AbsCont = AbsCont.Bot, - part: AbsPart = AbsPart.Bot, - astValue: AbsAstValue = AbsAstValue.Bot, - nt: AbsNt = AbsNt.Bot, - codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, - const: AbsConst = AbsConst.Bot, - math: AbsMath = AbsMath.Bot, - simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, - num: AbsNumber = AbsNumber.Bot, - bigInt: AbsBigInt = AbsBigInt.Bot, - str: AbsStr = AbsStr.Bot, - bool: AbsBool = AbsBool.Bot, - undef: AbsUndef = AbsUndef.Bot, - nullv: AbsNull = AbsNull.Bot, - absent: AbsAbsent = AbsAbsent.Bot, - ): Elem - - /** raw tuple of each simple value type */ - type RawTuple = ( - AbsComp, - AbsPureValue, - ) - - /** extractors */ - def unapply(elem: Elem): Option[RawTuple] - - /** transfer for variadic operation */ - def vopTransfer(vop: VOp, vs: List[Elem]): Elem - - /** transfer for mathematical operation */ - def mopTransfer(mop: MOp, vs: List[Elem]): Elem - - /** abstract value interfaces */ - extension (elem: Elem) { - - /** get key values */ - def keyValue: Elem - - /** bitwise operations */ - def &(that: Elem): Elem - def |(that: Elem): Elem - def ^(that: Elem): Elem - - /** comparison operations */ - def =^=(that: Elem): Elem - def ==^==(that: Elem): Elem - def <(that: Elem): Elem - - /** logical operations */ - def &&(that: Elem): Elem - def ||(that: Elem): Elem - def ^^(that: Elem): Elem - - /** numeric operations */ - def +(that: Elem): Elem - def sub(that: Elem): Elem - def /(that: Elem): Elem - def *(that: Elem): Elem - def %(that: Elem): Elem - def %%(that: Elem): Elem - def **(that: Elem): Elem - def <<(that: Elem): Elem - def >>>(that: Elem): Elem - def >>(that: Elem): Elem - - /** unary operations */ - def unary_- : Elem - def unary_! : Elem - def unary_~ : Elem - def abs: Elem - def floor: Elem - - /** type operations */ - def typeOf(st: AbsState): Elem - def typeCheck(tname: String, st: AbsState): Elem - - /** helper functions for abstract transfer */ - def convertTo(cop: COp, radix: Elem): Elem - def sourceText: Elem - def parse(rule: Elem): Elem - def duplicated(st: AbsState): Elem - def substring(from: Elem): Elem - def substring(from: Elem, to: Elem): Elem - def trim(leading: Boolean, trailing: Boolean): Elem - def clamp(lower: Elem, upper: Elem): Elem - def isArrayIndex: Elem - - /** prune abstract values */ - def pruneValue(r: Elem, positive: Boolean): Elem - def pruneField(field: String, r: Elem, positive: Boolean): Elem - def pruneType(r: Elem, positive: Boolean): Elem - def pruneTypeCheck(r: Elem, positive: Boolean): Elem - - /** single check */ - def isSingle: Boolean = elem.getSingle match - case One(_) => true - case _ => false - - /** get reachable address partitions */ - def reachableParts: Set[Part] - - /** completion helpers */ - def wrapCompletion: Elem - def unwrapCompletion: Elem - def isCompletion: Elem - def normalCompletion: Elem - def abruptCompletion: Elem - - /** absent helpers */ - def removeAbsent: Elem - def isAbsent: Elem - - /** refine receiver object */ - def refineThis(func: Func): Elem - - /** get syntactic SDO */ - def getSDO(method: String): List[(Func, Elem)] - - /** get lexical result */ - def getLexical(method: String): Elem - - /** getters */ - def comp: AbsComp - def pureValue: AbsPureValue - def clo: AbsClo - def cont: AbsCont - def part: AbsPart - def astValue: AbsAstValue - def nt: AbsNt - def codeUnit: AbsCodeUnit - def const: AbsConst - def math: AbsMath - def simpleValue: AbsSimpleValue - def number: AbsNumber - def bigInt: AbsBigInt - def str: AbsStr - def bool: AbsBool - def undef: AbsUndef - def nullv: AbsNull - def absent: AbsAbsent - def ty: ValueTy - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/value/TypeDomain.scala b/src/main/scala/esmeta/analyzer/domain/value/TypeDomain.scala deleted file mode 100644 index 66fdc8286d..0000000000 --- a/src/main/scala/esmeta/analyzer/domain/value/TypeDomain.scala +++ /dev/null @@ -1,655 +0,0 @@ -package esmeta.analyzer.domain.value - -import esmeta.analyzer.* -import esmeta.analyzer.domain.* -import esmeta.cfg.Func -import esmeta.es.* -import esmeta.ir.{COp, Name, VOp, MOp} -import esmeta.parser.ESValueParser -import esmeta.state.* -import esmeta.spec.{Grammar => _, *} -import esmeta.ty.* -import esmeta.ty.util.{Stringifier => TyStringifier} -import esmeta.util.* -import esmeta.util.Appender.* -import esmeta.util.BaseUtils.* -import scala.annotation.tailrec -import scala.collection.mutable.{Map => MMap, Set => MSet} - -/** type domain for values */ -object TypeDomain extends value.Domain { - - /** elements */ - case class Elem(ty: ValueTy) extends Appendable - - /** top element */ - lazy val Top: Elem = exploded("top abstract value") - - /** bottom element */ - val Bot: Elem = Elem(ValueTy()) - - /** abstraction functions */ - def alpha(vs: Iterable[AValue]): Elem = Elem(getValueTy(vs)) - - /** constructor with types */ - def apply(ty: Ty): Elem = ty match - case _: UnknownTy => Bot - case vty: ValueTy => Elem(vty) - - /** constructor for completions */ - def createCompletion( - ty: AbsValue, - value: AbsValue, - target: AbsValue, - ): Elem = - val consts = ty.ty.const - val normal = - if (consts contains "normal") value.ty.pureValue - else PureValueTy() - val abrupt = consts -- Fin("normal") - Elem(ValueTy(normal = normal, abrupt = abrupt)) - - /** predefined top values */ - lazy val compTop: Elem = notSupported("value.TypeDomain.compTop") - lazy val pureValueTop: Elem = notSupported("value.TypeDomain.pureValueTop") - lazy val cloTop: Elem = Elem(CloT) - lazy val contTop: Elem = Elem(ContT) - lazy val partTop: Elem = notSupported("value.TypeDomain.partTop") - lazy val astValueTop: Elem = Elem(AstT) - lazy val ntTop: Elem = notSupported("value.TypeDomain.ntTop") - lazy val codeUnitTop: Elem = Elem(CodeUnitT) - lazy val constTop: Elem = notSupported("value.TypeDomain.constTop") - lazy val mathTop: Elem = Elem(MathT) - lazy val simpleValueTop: Elem = - notSupported("value.TypeDomain.simpleValueTop") - lazy val numberTop: Elem = Elem(NumberT) - lazy val bigIntTop: Elem = Elem(BigIntT) - lazy val strTop: Elem = Elem(StrT) - lazy val boolTop: Elem = Elem(BoolT) - lazy val undefTop: Elem = Elem(UndefT) - lazy val nullTop: Elem = Elem(NullT) - lazy val absentTop: Elem = Elem(AbsentT) - - /** constructors */ - def apply( - comp: AbsComp, - pureValue: AbsPureValue, - clo: AbsClo, - cont: AbsCont, - part: AbsPart, - astValue: AbsAstValue, - nt: AbsNt, - codeUnit: AbsCodeUnit, - const: AbsConst, - math: AbsMath, - simpleValue: AbsSimpleValue, - num: AbsNumber, - bigInt: AbsBigInt, - str: AbsStr, - bool: AbsBool, - undef: AbsUndef, - nullv: AbsNull, - absent: AbsAbsent, - ): Elem = Top - - /** extractors */ - def unapply(elem: Elem): Option[RawTuple] = None - - /** appender */ - given rule: Rule[Elem] = (app, elem) => - import TyStringifier.given - app >> elem.ty - - /** transfer for variadic operation */ - def vopTransfer(vop: VOp, vs: List[Elem]): Elem = vop match - case VOp.Min | VOp.Max => mathTop - case VOp.Concat => strTop - - /** transfer for mathematical operation */ - def mopTransfer(mop: MOp, vs: List[Elem]): Elem = mathTop - - /** element interfaces */ - extension (elem: Elem) { - - /** get key values */ - def keyValue: Elem = notSupported("value.TypeDomain.Elem.keyValue") - - /** partial order */ - def ⊑(that: Elem): Boolean = elem.ty <= that.ty - - /** join operator */ - def ⊔(that: Elem): Elem = Elem(elem.ty || that.ty) - - /** meet operator */ - override def ⊓(that: Elem): Elem = Elem(elem.ty && that.ty) - - /** prune operator */ - override def --(that: Elem): Elem = Elem(elem.ty -- that.ty) - - /** concretization function */ - override def gamma: BSet[AValue] = Inf - - /** get single value */ - override def getSingle: Flat[AValue] = elem.ty.getSingle - - /** get reachable address partitions */ - def reachableParts: Set[Part] = Set() - - /** bitwise operations */ - def &(that: Elem): Elem = bitwiseOp(elem, that) - def |(that: Elem): Elem = bitwiseOp(elem, that) - def ^(that: Elem): Elem = bitwiseOp(elem, that) - - /** comparison operations */ - def =^=(that: Elem): Elem = - (elem.getSingle, that.getSingle) match - case (Zero, _) | (_, Zero) => Bot - case (One(l), One(r)) => Elem(BoolT(l == r)) - case _ if (elem ⊓ that).isBottom => Elem(FalseT) - case _ => boolTop - def ==^==(that: Elem): Elem = numericCompareOP(elem, that) - def <(that: Elem): Elem = numericCompareOP(elem, that) - - /** logical operations */ - def &&(that: Elem): Elem = logicalOp(_ && _)(elem, that) - def ||(that: Elem): Elem = logicalOp(_ || _)(elem, that) - def ^^(that: Elem): Elem = logicalOp(_ ^ _)(elem, that) - - /** numeric operations */ - def +(that: Elem): Elem = numericOp(elem, that) - def sub(that: Elem): Elem = numericOp(elem, that) - def /(that: Elem): Elem = numericOp(elem, that) - def *(that: Elem): Elem = numericOp(elem, that) - def %(that: Elem): Elem = numericOp(elem, that) - def %%(that: Elem): Elem = numericOp(elem, that) - def **(that: Elem): Elem = numericOp(elem, that) - def <<(that: Elem): Elem = mathOp(elem, that) ⊔ bigIntOp(elem, that) - def >>(that: Elem): Elem = mathOp(elem, that) ⊔ bigIntOp(elem, that) - def >>>(that: Elem): Elem = mathOp(elem, that) - - /** unary operations */ - def unary_- : Elem = numericUnaryOp(elem) - def unary_! : Elem = logicalUnaryOp(!_)(elem) - def unary_~ : Elem = numericUnaryOp(elem) - def abs: Elem = mathTop - def floor: Elem = mathTop - - /** type operations */ - def typeOf(st: AbsState): Elem = - val ty = elem.ty - var names: Set[String] = Set() - if ( - ty.name.set match - case Inf => true - case Fin(set) => set.exists(cfg.tyModel.isSubTy(_, "Object")) - ) names += "Object" - if (ty.symbol) names += "Symbol" - if (!ty.number.isBottom) names += "Number" - if (ty.bigInt) names += "BigInt" - if (!ty.str.isBottom) names += "String" - if (!ty.bool.isBottom) names += "Boolean" - if (ty.undef) names += "Undefined" - if (ty.nullv) names += "Null" - Elem(StrT(names)) - - /** type check */ - def typeCheck(tname: String, st: AbsState): Elem = - val names = instanceNameSet(elem.ty) - if (names.isEmpty) Bot - else if (names == Set(tname)) Elem(TrueT) - else if (!names.contains(tname)) Elem(FalseT) - else boolTop - - /** helper functions for abstract transfer */ - def convertTo(cop: COp, radix: Elem): Elem = - val ty = elem.ty - Elem(cop match - case COp.ToApproxNumber if (!ty.math.isBottom) => - NumberT - case COp.ToNumber - if (!ty.math.isBottom || !ty.str.isBottom || !ty.number.isBottom) => - NumberT - case COp.ToBigInt - if (!ty.math.isBottom || !ty.str.isBottom || !ty.number.isBottom || ty.bigInt) => - BigIntT - case COp.ToMath - if (!ty.math.isBottom || !ty.number.isBottom || ty.bigInt) => - MathT - case COp.ToStr(_) - if (!ty.str.isBottom || !ty.number.isBottom || ty.bigInt) => - StrT - case _ => ValueTy(), - ) - def sourceText: Elem = strTop - def parse(rule: Elem): Elem = rule.ty.nt match - case Inf => exploded("too imprecise nt rule for parsing") - case Fin(set) => - Elem(ValueTy(astValue = AstNameTy((for { - nt <- set - name = nt.name - } yield name).toSet))) - def duplicated(st: AbsState): Elem = boolTop - def substring(from: Elem): Elem = strTop - def substring(from: Elem, to: Elem): Elem = strTop - def trim(leading: Boolean, trailing: Boolean): Elem = strTop - def clamp(lower: Elem, upper: Elem): Elem = mathTop - def isArrayIndex: Elem = boolTop - - /** prune abstract values */ - def pruneValue(r: Elem, positive: Boolean): Elem = - if (positive) elem ⊓ r - else if (r.isSingle) elem -- r - else elem - def pruneField(field: String, r: Elem, positive: Boolean): Elem = - field match - case "Value" => - val normal = ty.normal.prune(r.ty.pureValue, positive) - Elem(ty.copy(comp = CompTy(normal, ty.abrupt))) - case "Type" => - Elem(r.ty.const.getSingle match - case One("normal") => - if (positive) ValueTy(normal = ty.normal) - else ValueTy(abrupt = ty.abrupt) - case One(tname) => - if (positive) ValueTy(abrupt = Fin(tname)) - else ValueTy(normal = ty.normal, abrupt = ty.abrupt -- Fin(tname)) - case _ => ty, - ) - case _ => elem - def pruneType(r: Elem, positive: Boolean): Elem = - r.ty.str.getSingle match - case One(tname) => - val that = Elem(tname match - case "Object" => NameT("Object") - case "Symbol" => SymbolT - case "Number" => NumberT - case "BigInt" => BigIntT - case "String" => StrT - case "Boolean" => BoolT - case "Undefined" => UndefT - case "Null" => NullT - case _ => ValueTy(), - ) - if (positive) elem ⊓ that else elem -- that - case _ => elem - def pruneTypeCheck(r: Elem, positive: Boolean): Elem = (for { - tname <- r.getSingle match - case One(Str(s)) => Some(s) - case One(Nt(n, _)) => Some(n) - case _ => None - if cfg.tyModel.infos.contains(tname) - } yield { - if (positive) Elem(NameT(tname)) - else elem -- Elem(NameT(tname)) - }).getOrElse(elem) - - /** completion helpers */ - def wrapCompletion: Elem = - val ty = elem.ty - Elem( - ValueTy( - normal = ty.normal || ty.pureValue, - abrupt = ty.abrupt, - ), - ) - def unwrapCompletion: Elem = - val ty = elem.ty - Elem(ValueTy(pureValue = ty.normal || ty.pureValue)) - def isCompletion: Elem = - val ty = elem.ty - var bs: Set[Boolean] = Set() - if (!ty.comp.isBottom) bs += true - if (!ty.pureValue.isBottom) bs += false - Elem(ValueTy(bool = BoolTy(bs))) - def normalCompletion: Elem = Elem(ValueTy(normal = elem.ty.pureValue)) - def abruptCompletion: Elem = Elem(ValueTy(abrupt = elem.ty.abrupt)) - - /** absent helpers */ - def removeAbsent: Elem = Elem(elem.ty -- AbsentT) - def isAbsent: Elem = - var bs: Set[Boolean] = Set() - if (elem.ty.absent) bs += true - if (!elem.removeAbsent.ty.isBottom) bs += false - Elem(BoolT(bs)) - - /** refine receiver object */ - def refineThis(func: Func): Elem = elem - - /** get lexical result */ - def getLexical(method: String): Elem = Elem( - if (elem.ty.astValue.isBottom) ValueTy() - else - method match - case "SV" | "TRV" | "StringValue" => StrT - case "IdentifierCodePoints" => StrT - case "MV" | "NumericValue" => NumberT || BigIntT - case "TV" => StrT // XXX ignore UndefT case - case "BodyText" | "FlagText" => StrT - case "Contains" => BoolT - case _ => ValueTy(), - ) - - /** get syntactic SDO */ - def getSDO(method: String): List[(Func, Elem)] = elem.ty.astValue match - case AstTopTy => - for { - func <- cfg.funcs if func.isSDO - List(_, newMethod) <- allSdoPattern.unapplySeq(func.name) - if newMethod == method - } yield (func, Elem(AstT)) - case AstNameTy(names) => - for { - name <- names.toList - pair <- astSdoCache((name, method)) - } yield pair - case AstSingleTy(name, idx, subIdx) => - synSdoCache((name, idx, subIdx, method)) - - /** getters */ - def comp: AbsComp = notSupported("value.TypeDomain.Elem.comp") - def pureValue: AbsPureValue = - notSupported("value.TypeDomain.Elem.pureValue") - def clo: AbsClo = ty.clo match - case Inf => AbsClo.Top - case Fin(set) => - AbsClo(for { - name <- set.toList - if cfg.fnameMap.contains(name) - } yield AClo(cfg.fnameMap(name), Map())) // TODO captured - def cont: AbsCont = ty.cont match - case Inf => AbsCont.Top - case Fin(set) => - AbsCont(for { - fid <- set.toList - node = cfg.nodeMap(fid) - func = cfg.funcOf(node) - } yield ACont(NodePoint(func, node, View()), Map())) // TODO captured - def part: AbsPart = notSupported("value.TypeDomain.Elem.part") - def astValue: AbsAstValue = notSupported("value.TypeDomain.Elem.astValue") - def nt: AbsNt = notSupported("value.TypeDomain.Elem.nt") - def codeUnit: AbsCodeUnit = notSupported("value.TypeDomain.Elem.codeUnit") - def const: AbsConst = notSupported("value.TypeDomain.Elem.const") - def math: AbsMath = notSupported("value.TypeDomain.Elem.math") - def simpleValue: AbsSimpleValue = - notSupported("value.TypeDomain.Elem.simpleValue") - def number: AbsNumber = notSupported("value.TypeDomain.Elem.number") - def bigInt: AbsBigInt = notSupported("value.TypeDomain.Elem.bigInt") - def str: AbsStr = notSupported("value.TypeDomain.Elem.str") - def bool: AbsBool = notSupported("value.TypeDomain.Elem.undef") - def undef: AbsUndef = notSupported("value.TypeDomain.Elem.nullv") - def nullv: AbsNull = notSupported("value.TypeDomain.Elem.absent") - def absent: AbsAbsent = notSupported("value.TypeDomain.Elem.ty") - def ty: ValueTy = elem.ty - } - - // --------------------------------------------------------------------------- - // private helpers - // --------------------------------------------------------------------------- - // value type getter - private def getValueTy(vs: Iterable[AValue]): ValueTy = - vs.foldLeft(ValueTy()) { case (vty, v) => vty || getValueTy(v) } - - // value type getter - private def getValueTy(v: AValue): ValueTy = v match - case AComp(CONST_NORMAL, v, _) => NormalT(getValueTy(v)) - case _: AComp => AbruptT - case AClo(func, _) => CloT(func.name) - case ACont(target, _) => ContT(target.node.id) - case AstValue(ast) => AstT(ast.name) - case nt: Nt => NtT(nt) - case CodeUnit(_) => CodeUnitT - case Const(name) => ConstT(name) - case Math(n) => MathT(n) - case n: Number => NumberT(n) - case BigInt(_) => BigIntT - case Str(n) => StrT(n) - case Bool(true) => TrueT - case Bool(false) => FalseT - case Undef => UndefT - case Null => NullT - case Absent => AbsentT - case v => notSupported(s"impossible to convert to pure type ($v)") - - // mathematical operator helper - private lazy val mathOp: (Elem, Elem) => Elem = (l, r) => - if (!l.ty.math.isBottom && !r.ty.math.isBottom) mathTop - else Bot - - // number operator helper - private lazy val numberOp: (Elem, Elem) => Elem = (l, r) => - if (!l.ty.number.isBottom && !r.ty.number.isBottom) numberTop - else Bot - - // big integer operator helper - private lazy val bigIntOp: (Elem, Elem) => Elem = (l, r) => - if (l.ty.bigInt && r.ty.bigInt) bigIntTop - else Bot - - // bitwise operator helper - private lazy val bitwiseOp: (Elem, Elem) => Elem = (l, r) => - mathOp(l, r) ⊔ bigIntOp(l, r) - - // logical unary operator helper - private def logicalUnaryOp( - op: Boolean => Boolean, - ): Elem => Elem = - b => Elem(ValueTy(bool = BoolTy(for (x <- b.ty.bool.set) yield op(x)))) - - // logical operator helper - private def logicalOp( - op: (Boolean, Boolean) => Boolean, - ): (Elem, Elem) => Elem = (l, r) => - Elem(ValueTy(bool = BoolTy(for { - x <- l.ty.bool.set - y <- r.ty.bool.set - } yield op(x, y)))) - - // numeric comparison operator helper - private lazy val numericCompareOP: (Elem, Elem) => Elem = (l, r) => - Elem( - ValueTy( - bool = BoolTy( - if ( - ( - (!l.ty.math.isBottom || !l.ty.number.isBottom) && - (!r.ty.math.isBottom || !r.ty.number.isBottom) - ) || (l.ty.bigInt && r.ty.bigInt) - ) Set(true, false) - else Set(), - ), - ), - ) - - // numeric unary operator helper - private lazy val numericUnaryOp: Elem => Elem = x => - var res: Elem = Bot - if (!x.ty.math.isBottom) res ⊔= mathTop - if (!x.ty.number.isBottom) res ⊔= numberTop - if (x.ty.bigInt) res ⊔= bigIntTop - res - - // numeric operator helper - private lazy val numericOp: (Elem, Elem) => Elem = (l, r) => - Elem( - ValueTy( - math = l.ty.math && r.ty.math, - number = l.ty.number && r.ty.number, - bigInt = l.ty.bigInt && r.ty.bigInt, - ), - ) - - /** instance name */ - private def instanceNameSet(ty: ValueTy): Set[String] = - var names: Set[String] = Set() - for (name <- ty.name.set) - names ++= cfg.tyModel.subTys.getOrElse(name, Set(name)) - names ++= ancestors(name) - if (!ty.astValue.isBottom) ty.astValue match - case AstTopTy => - names ++= astChildMap.keySet + "ParseNode" + "Nonterminal" - case ty: AstNonTopTy => - val astNames = ty.toName.names - names += "ParseNode" - for (astName <- astNames) - names ++= astChildMap.getOrElse(astName, Set(astName)) - names - - /** get ancestor types */ - private def ancestors(tname: String): Set[String] = - ancestorList(tname).toSet - private def ancestorList(tname: String): List[String] = - tname :: parent(tname).map(ancestorList).getOrElse(Nil) - - /** get parent types */ - private def parent(name: String): Option[String] = for { - TyInfo(parent, _, _) <- cfg.tyModel.infos.get(name) - p <- parent - } yield p - - /** ast type check helper */ - lazy val astDirectChildMap: Map[String, Set[String]] = - (cfg.grammar.prods.map { - case Production(lhs, _, _, rhsList) => - val name = lhs.name - val subs = rhsList.collect { - case Rhs(_, List(Nonterminal(name, _)), _) => name - }.toSet - name -> subs - }).toMap - lazy val astChildMap: Map[String, Set[String]] = - var descs = Map[String, Set[String]]() - def aux(name: String): Set[String] = descs.get(name) match - case Some(set) => set - case None => - val set = (for { - sub <- astDirectChildMap.getOrElse(name, Set()) - elem <- aux(sub) - } yield elem) + name - descs += name -> set - set - cfg.grammar.prods.foreach(prod => aux(prod.name)) - descs - - /** sdo access helper */ - private lazy val astSdoCache: ((String, String)) => List[(Func, Elem)] = - cached[(String, String), List[(Func, Elem)]] { - case (name, method) => - val result = (for { - (fid, thisTy, hint) <- sdoMap.getOrElse(name, Set()) if hint == method - } yield (cfg.funcMap(fid), Elem(thisTy))).toList - if (result.isEmpty) { - if (defaultSdos contains method) { - val defaultFunc = cfg.fnameMap(s".$method") - for { - (rhs, idx) <- cfg.grammar.nameMap(name).rhsList.zipWithIndex - subIdx <- (0 until rhs.countSubs) - } yield (defaultFunc, Elem(AstSingleT(name, idx, subIdx))) - } else Nil - } else result - } - private lazy val synSdoCache = - cached[(String, Int, Int, String), List[(Func, Elem)]] { - case (name, idx, subIdx, method) => - val result = (for { - (fid, thisTy, hint) <- sdoMap.getOrElse(s"$name[$idx,$subIdx]", Set()) - if hint == method - } yield (cfg.funcMap(fid), Elem(thisTy))).toList - if (result.isEmpty) { - if (defaultSdos contains method) { - val defaultFunc = cfg.fnameMap(s".$method") - List((defaultFunc, Elem(AstSingleT(name, idx, subIdx)))) - } else Nil - } else result - } - - /** sdo with default case */ - val defaultSdos = List( - "Contains", - "AllPrivateIdentifiersValid", - "ContainsArguments", - ) - - private lazy val allSdoPattern = """(|\w+\[\d+,\d+\])\.(\w+)""".r - private lazy val sdoPattern = """(\w+)\[(\d+),(\d+)\]\.(\w+)""".r - private lazy val sdoMap = { - val edges: MMap[String, MSet[String]] = MMap() - for { - prod <- cfg.grammar.prods - name = prod.name if !(cfg.grammar.lexicalNames contains name) - (rhs, idx) <- prod.rhsList.zipWithIndex - subIdx <- (0 until rhs.countSubs) - } { - val syntacticName = s"$name[$idx,$subIdx]" - edges += (syntacticName -> MSet(name)) - rhs.getNts(subIdx) match - case List(Some(chain)) => - if (edges contains chain) edges(chain) += syntacticName - else edges(chain) = MSet(syntacticName) - case _ => - } - val worklist = QueueWorklist[String](List()) - val infos: MMap[String, MSet[(Int, ValueTy, String)]] = MMap() - var defaultInfos: MMap[String, MSet[(Int, ValueTy, String)]] = MMap() - for { - func <- cfg.funcs if func.isSDO - isDefaultSdo = func.name.startsWith("") if !isDefaultSdo - List(name, idxStr, subIdxStr, method) <- sdoPattern.unapplySeq(func.name) - (idx, subIdx) = (idxStr.toInt, subIdxStr.toInt) - key = s"$name[$idx,$subIdx]" - } { - val newInfo = (func.id, AstSingleT(name, idx, subIdx), method) - val isDefaultSdo = defaultSdos contains method - - // update target info - val targetInfos = if (isDefaultSdo) defaultInfos else infos - if (targetInfos contains key) targetInfos(key) += newInfo - else targetInfos(key) = MSet(newInfo) - if (targetInfos contains name) targetInfos(name) += newInfo - else targetInfos(name) = MSet(newInfo) - - // propagate chain production - if (!isDefaultSdo) worklist += name - } - - // record original infos - val origInfos = (for { (k, set) <- infos } yield k -> set.toSet).toMap - - // propagate chain productions - @tailrec - def aux(): Unit = worklist.next match - case Some(key) => - val childInfo = infos.getOrElse(key, MSet()) - for { - next <- edges.getOrElse(key, MSet()) - info = infos.getOrElse(next, MSet()) - oldInfoSize = info.size - - newInfo = - // A[i,j] -> A - if (key endsWith "]") info ++ childInfo - // A.method -> B[i,j].method - // only if B[i,j].method not exists (chain production) - else { - val origInfo = origInfos.getOrElse(next, Set()) - info ++ (for { - triple <- childInfo - if !(origInfo.exists(_._3 == triple._3)) - } yield triple) - } - - _ = infos(next) = newInfo - if newInfo.size > oldInfoSize - } worklist += next - aux() - case None => /* do nothing */ - aux() - - // merge default infos - (for { - key <- infos.keySet ++ defaultInfos.keySet - info = infos.getOrElse(key, MSet()) - defaultInfo = defaultInfos.getOrElse(key, MSet()) - finalInfo = (info ++ defaultInfo).toSet - } yield key -> finalInfo).toMap - } -} diff --git a/src/main/scala/esmeta/analyzer/domain/value/ValueBasicDomain.scala b/src/main/scala/esmeta/analyzer/domain/value/ValueBasicDomain.scala new file mode 100644 index 0000000000..5980c0d622 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/value/ValueBasicDomain.scala @@ -0,0 +1,537 @@ +package esmeta.analyzer.domain.value + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.cfg.Func +import esmeta.es.* +import esmeta.state.* +import esmeta.ty.* +import esmeta.ir.{COp, Name, VOp, MOp} +import esmeta.parser.ESValueParser +import esmeta.util.* +import esmeta.util.Appender.* + +trait ValueBasicDomainDecl { self: Self => + + /** basic domain for values */ + object ValueBasicDomain extends ValueDomain { + + /** elements */ + case class Elem( + comp: AbsComp = AbsComp.Bot, + pureValue: AbsPureValue = AbsPureValue.Bot, + ) extends Appendable + + /** top element */ + lazy val Top: Elem = exploded("top abstract value") + + /** bottom element */ + val Bot: Elem = Elem() + + /** abstraction functions */ + def alpha(xs: Iterable[AValue]): Elem = Elem( + AbsComp(xs.collect { case x: AComp => x }), + AbsPureValue(xs.collect { case x: APureValue => x }), + ) + + /** constructor with types */ + def apply(ty: Ty): Elem = Top + + /** constructor for completions */ + def createCompletion( + ty: AbsValue, + value: AbsValue, + target: AbsValue, + ): Elem = Elem(AbsComp(ty, value, target)) + + /** predefined top values */ + def compTop: Elem = Bot.copy(comp = AbsComp.Top) + def pureValueTop: Elem = Bot.copy(pureValue = AbsPureValue.Top) + val cloTop: Elem = Bot.copy(pureValue = AbsPureValue.cloTop) + val contTop: Elem = Bot.copy(pureValue = AbsPureValue.contTop) + val partTop: Elem = Bot.copy(pureValue = AbsPureValue.partTop) + val astValueTop: Elem = Bot.copy(pureValue = AbsPureValue.astValueTop) + val ntTop: Elem = Bot.copy(pureValue = AbsPureValue.ntTop) + val codeUnitTop: Elem = Bot.copy(pureValue = AbsPureValue.codeUnitTop) + val constTop: Elem = Bot.copy(pureValue = AbsPureValue.constTop) + val mathTop: Elem = Bot.copy(pureValue = AbsPureValue.mathTop) + val simpleValueTop: Elem = Bot.copy(pureValue = AbsPureValue.simpleValueTop) + val numberTop: Elem = Bot.copy(pureValue = AbsPureValue.numberTop) + val bigIntTop: Elem = Bot.copy(pureValue = AbsPureValue.bigIntTop) + val strTop: Elem = Bot.copy(pureValue = AbsPureValue.strTop) + val boolTop: Elem = Bot.copy(pureValue = AbsPureValue.boolTop) + val undefTop: Elem = Bot.copy(pureValue = AbsPureValue.undefTop) + val nullTop: Elem = Bot.copy(pureValue = AbsPureValue.nullTop) + val absentTop: Elem = Bot.copy(pureValue = AbsPureValue.absentTop) + + /** constructors */ + def apply( + comp: AbsComp = AbsComp.Bot, + pureValue: AbsPureValue = AbsPureValue.Bot, + clo: AbsClo = AbsClo.Bot, + cont: AbsCont = AbsCont.Bot, + part: AbsPart = AbsPart.Bot, + astValue: AbsAstValue = AbsAstValue.Bot, + nt: AbsNt = AbsNt.Bot, + codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, + const: AbsConst = AbsConst.Bot, + math: AbsMath = AbsMath.Bot, + simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, + num: AbsNumber = AbsNumber.Bot, + bigInt: AbsBigInt = AbsBigInt.Bot, + str: AbsStr = AbsStr.Bot, + bool: AbsBool = AbsBool.Bot, + undef: AbsUndef = AbsUndef.Bot, + nullv: AbsNull = AbsNull.Bot, + absent: AbsAbsent = AbsAbsent.Bot, + ): Elem = Elem( + comp, + pureValue ⊔ AbsPureValue( + clo, + cont, + part, + astValue, + nt, + codeUnit, + const, + math, + simpleValue ⊔ AbsSimpleValue( + num, + bigInt, + str, + bool, + undef, + nullv, + absent, + ), + ), + ) + + /** extractors */ + def unapply(elem: Elem): Option[RawTuple] = Some( + ( + elem.comp, + elem.pureValue, + ), + ) + + /** appender */ + given rule: Rule[Elem] = (app, elem) => + if (!elem.isBottom) { + val Elem(comp, pureValue) = elem + var strs = Vector[String]() + if (!comp.isBottom) strs :+= comp.toString + if (!pureValue.isBottom) strs :+= pureValue.toString + app >> strs.mkString(", ") + } else app >> "⊥" + + /** transfer for variadic operation */ + def vopTransfer(vop: VOp, vs: List[Elem]): Elem = + import VOp.* + // helpers + def asMath(av: Elem): Option[BigDecimal] = av.getSingle match + case Many => exploded("vop transfer") + case One(Math(n)) => Some(n) + case _ => None + def asStr(av: Elem): Option[String] = av.getSingle match + case Many => exploded("vop transfer") + case One(Str(s)) => Some(s) + case One(CodeUnit(cu)) => Some(cu.toString) + case _ => None + // transfer body + if (!vs.exists(_.isBottom)) vop match + case Min => + val set = scala.collection.mutable.Set[Elem]() + if (vs.exists(apply(NEG_INF) ⊑ _)) set += apply(NEG_INF) + val filtered = vs.filter((v) => !(apply(POS_INF) ⊑ v)) + if (filtered.isEmpty) set += apply(POS_INF) + set += doVopTransfer(asMath, _ min _, apply, filtered) + set.foldLeft(Bot)(_ ⊔ _) + case Max => + val set = scala.collection.mutable.Set[Elem]() + if (vs.exists(apply(POS_INF) ⊑ _)) set += apply(POS_INF) + val filtered = vs.filter((v) => !(apply(NEG_INF) ⊑ v)) + if (filtered.isEmpty) set += apply(NEG_INF) + set += doVopTransfer(asMath, _ min _, apply, filtered) + set.foldLeft(Bot)(_ ⊔ _) + case Concat => doVopTransfer[String](asStr, _ + _, apply, vs) + else Bot + + /** helpers for make transition for variadic operators */ + protected def doVopTransfer[T]( + f: Elem => Option[T], + op: (T, T) => T, + g: T => Elem, + vs: List[Elem], + ): Elem = + val vst = vs.map(f).flatten + if (vst.size != vs.size) Bot + else g(vst.reduce(op)) + + /** transfer for mathematical operation */ + def mopTransfer(mop: MOp, vs: List[Elem]): Elem = mathTop + + /** element interfaces */ + extension (elem: Elem) { + + /** get key values */ + def keyValue: Elem = apply(part = part, str = str) + + /** partial order */ + def ⊑(that: Elem): Boolean = + elem.comp ⊑ that.comp && + elem.pureValue ⊑ that.pureValue + + /** join operator */ + def ⊔(that: Elem): Elem = Elem( + elem.comp ⊔ that.comp, + elem.pureValue ⊔ that.pureValue, + ) + + /** meet operator */ + override def ⊓(that: Elem): Elem = Elem( + elem.comp ⊓ that.comp, + elem.pureValue ⊓ that.pureValue, + ) + + /** prune operator */ + override def --(that: Elem): Elem = Elem( + elem.comp -- that.comp, + elem.pureValue -- that.pureValue, + ) + + /** concretization function */ + override def gamma: BSet[AValue] = + elem.comp.gamma ⊔ + elem.pureValue.gamma + + /** get single value */ + override def getSingle: Flat[AValue] = + elem.comp.getSingle ⊔ + elem.pureValue.getSingle + + /** get reachable address partitions */ + def reachableParts: Set[Part] = + comp.reachableParts ++ pureValue.reachableParts + + /** bitwise operations */ + def &(that: Elem): Elem = ??? // TODO + def |(that: Elem): Elem = ??? // TODO + def ^(that: Elem): Elem = ??? // TODO + + /** comparison operations */ + def =^=(that: Elem): Elem = + apply(bool = (elem.getSingle, that.getSingle) match + case (Zero, _) | (_, Zero) => AbsBool.Bot + case (One(l), One(r)) => AbsBool(Bool(l == r)) + case _ => if ((elem ⊓ that).isBottom) AF else AB, + ) + def ==^==(that: Elem): Elem = ??? // TODO + def <(that: Elem): Elem = ??? // TODO + + /** logical operations */ + def &&(that: Elem): Elem = ??? // TODO + def ||(that: Elem): Elem = ??? // TODO + def ^^(that: Elem): Elem = ??? // TODO + + /** numeric operations */ + def +(that: Elem): Elem = ??? // TODO + // AbsValue( + // str = ( + // (left.str plus right.str) ⊔ + // (left.str plusNum right.num) + // ), + // num = ( + // (left.num plus right.num) ⊔ + // (right.num plusInt left.int) ⊔ + // (left.num plusInt right.int) + // ), + // int = left.int plus right.int, + // bigInt = left.bigInt plus right.bigInt, + // ) + def sub(that: Elem): Elem = ??? // TODO + def /(that: Elem): Elem = ??? // TODO + def *(that: Elem): Elem = ??? // TODO + // AbsValue( + // num = ( + // (left.num mul right.num) ⊔ + // (right.num mulInt left.int) ⊔ + // (left.num mulInt right.int) + // ), + // int = left.int mul right.int, + // bigInt = left.bigInt mul right.bigInt, + // ) + def %(that: Elem): Elem = ??? // TODO + def %%(that: Elem): Elem = ??? // TODO + def **(that: Elem): Elem = ??? // TODO + def <<(that: Elem): Elem = ??? // TODO + def >>>(that: Elem): Elem = ??? // TODO + def >>(that: Elem): Elem = ??? // TODO + + /** unary operations */ + def unary_- : Elem = ??? // TODO + def unary_! : Elem = apply(bool = !elem.bool) + def unary_~ : Elem = ??? // TODO + def abs: Elem = ??? // TODO + def floor: Elem = ??? // TODO + + /** type operations */ + def typeOf(st: AbsState): Elem = + var set = Set[String]() + if (!elem.number.isBottom) set += "Number" + if (!elem.bigInt.isBottom) set += "BigInt" + if (!elem.str.isBottom) set += "String" + if (!elem.bool.isBottom) set += "Boolean" + if (!elem.undef.isBottom) set += "Undefined" + if (!elem.nullv.isBottom) set += "Null" + if (!elem.part.isBottom) for (part <- elem.part) { + val tname = st.get(part).getTy match + case tname if cfg.tyModel.isSubTy(tname, "Object") => "Object" + case tname => tname + set += tname + } + apply(str = AbsStr(set.map(Str.apply))) + + /** type check */ + def typeCheck(tname: String, st: AbsState): Elem = + var bv: AbsBool = AbsBool.Bot + if (!elem.number.isBottom) bv ⊔= AbsBool(Bool(tname == "Number")) + if (!elem.bigInt.isBottom) bv ⊔= AbsBool(Bool(tname == "BigInt")) + if (!elem.str.isBottom) bv ⊔= AbsBool(Bool(tname == "String")) + if (!elem.bool.isBottom) bv ⊔= AbsBool(Bool(tname == "Boolean")) + if (!elem.const.isBottom) + bv ⊔= AbsBool(Bool(tname == "Constant")) + if (!elem.comp.isBottom) + bv ⊔= AbsBool(Bool(tname == "CompletionRecord")) + if (!elem.undef.isBottom) + bv ⊔= AbsBool(Bool(tname == "Undefined")) + if (!elem.nullv.isBottom) bv ⊔= AbsBool(Bool(tname == "Null")) + if (!elem.clo.isBottom) + bv ⊔= AbsBool(Bool(tname == "AbstractClosure")) + elem.astValue.getSingle match + case Zero => /* do nothing */ + case Many => bv = AB + case One(AstValue(ast)) => + bv ⊔= AbsBool( + Bool(tname == "ParseNode" || (ast.types contains tname)), + ) + for (part <- elem.part) { + val newTName = st.get(part).getTy + bv ⊔= AbsBool( + Bool( + newTName == tname || cfg.tyModel.isSubTy(newTName, tname), + ), + ) + } + apply(bool = bv) + + /** helper functions for abstract transfer */ + def convertTo(cop: COp, radix: Elem): Elem = + import COp.* + var newV = Bot + for (CodeUnit(cu) <- elem.codeUnit) newV ⊔= (cop match + case ToMath => apply(Math(cu.toInt)) + case _ => Bot + ) + for (Math(n) <- elem.math) newV ⊔= (cop match + case ToApproxNumber => apply(Number(n.toDouble)) + case ToNumber => apply(Number(n.toDouble)) + case ToBigInt => apply(BigInt(n.toBigInt)) + case ToMath => apply(Math(n)) + case _ => Bot + ) + for (Str(s) <- elem.str) newV ⊔= (cop match + case ToNumber => apply(Number(ESValueParser.str2Number(s))) + case ToBigInt => apply(ESValueParser.str2bigInt(s)) + case _: ToStr => apply(Str(s)) + case _ => Bot + ) + for (Number(d) <- elem.number) newV ⊔= (cop match + case ToMath => apply(Math(d)) + case _: ToStr => + radix.asInt.foldLeft(Bot)((v, n) => v ⊔ apply(toStringHelper(d, n))) + case ToNumber => apply(Number(d)) + case ToBigInt => apply(BigInt(BigDecimal.exact(d).toBigInt)) + case _ => Bot + ) + for (BigInt(b) <- elem.bigInt) newV ⊔= (cop match + case ToMath => apply(Math(b)) + case _: ToStr => + radix.asInt.foldLeft(Bot)((v, n) => v ⊔ apply(Str(b.toString(n)))) + case ToBigInt => apply(BigInt(b)) + case _ => Bot + ) + newV + def sourceText: Elem = apply(str = + AbsStr( + elem.astValue.toList.map(x => + Str(x.ast.toString(grammar = Some(cfg.grammar)).trim), + ), + ), + ) + def parse(rule: Elem): Elem = + var newV: Elem = Bot + // codes + var codes: Set[(String, List[Boolean])] = Set() + for (Str(s) <- elem.str) codes += (s, List()) + for (AstValue(ast) <- elem.astValue) { + val code = ast.toString(grammar = Some(cfg.grammar)) + val args = ast match + case syn: Syntactic => syn.args + case _ => List() + codes += (code, args) + } + // parse + for { + Nt(name, params) <- rule.nt + (str, args) <- codes + parseArgs = if (params.isEmpty) args else params + } newV ⊔= apply(cfg.esParser(name, parseArgs).from(str)) + // result + newV + def duplicated(st: AbsState): Elem = + apply(bool = elem.part.foldLeft(AbsBool.Bot: AbsBool) { + case (avb, part) => avb ⊔ st.get(part).duplicated + }) + def substring(from: Elem): Elem = + (elem.getSingle, from.getSingle) match + case (Zero, _) | (_, Zero) => Bot + case (Many, _) | (_, Many) => exploded("ESubstring") + case (One(Str(s)), One(Math(f))) if f.isValidInt => + apply(s.substring(f.toInt)) + case _ => Bot + + def substring(from: Elem, to: Elem): Elem = + (elem.getSingle, from.getSingle, to.getSingle) match + case (Zero, _, _) | (_, Zero, _) | (_, _, Zero) => Bot + case (Many, _, _) | (_, Many, _) | (_, _, Many) => + exploded("ESubstring") + case ( + One(Str(s)), + One(Math(f)), + One(Math(t)), + ) if f.isValidInt => + if (s.length < t) apply(s.substring(f.toInt)) + else if (t.isValidInt) apply(s.substring(f.toInt, t.toInt)) + else Bot + case _ => Bot + def trim(leading: Boolean, trailing: Boolean): Elem = elem.getSingle match + case Many => exploded("ETrim") + case One(Str(s)) => + apply( + if (leading && trailing) s.trim + else if (leading) s.replaceAll("^\\s+", "") + else if (trailing) s.replaceAll("\\s+$", "") + else s, + ) + case _ => Bot + def clamp(lower: Elem, upper: Elem): Elem = + (elem.getSingle, lower.getSingle, upper.getSingle) match + case (Zero, _, _) | (_, Zero, _) | (_, _, Zero) => Bot + case (Many, _, _) | (_, Many, _) | (_, _, Many) => exploded("EClamp") + case ( + One(target), + One(Math(l)), + One(Math(u)), + ) => + target match + case Math(t) => + apply( + if (t < l) Math(l) + else if (t > u) Math(u) + else Math(t), + ) + case POS_INF => apply(Math(u)) + case NEG_INF => apply(Math(l)) + case _ => Bot + case _ => Bot + def isArrayIndex: Elem = elem.getSingle match + case Zero => Bot + case One(Str(s)) => + val d = ESValueParser.str2Number(s) + val ds = toStringHelper(d) + val UPPER = (1L << 32) - 1 + val l = d.toLong + apply(ds == s && 0 <= l && d == l && l < UPPER) + case One(_) => apply(F) + case Many => exploded("EIsArrayIndex") + + /** prune abstract values */ + def pruneValue(r: Elem, positive: Boolean): Elem = elem + def pruneField(field: String, r: Elem, positive: Boolean): Elem = elem + def pruneType(r: Elem, positive: Boolean): Elem = elem + def pruneTypeCheck(r: Elem, positive: Boolean): Elem = elem + + /** completion helpers */ + def wrapCompletion: Elem = wrapCompletion("normal") + def wrapCompletion(ty: String): Elem = apply(comp = { + if (!pureValue.isBottom) + comp ⊔ AbsComp( + Map(ty -> AbsComp.Result(pureValue, AbsPureValue(CONST_EMPTY))), + ) + else comp + }) + def unwrapCompletion: Elem = + Elem(pureValue = comp.normal.value ⊔ elem.pureValue) + def isCompletion: Elem = + var b: AbsBool = AbsBool.Bot + if (!comp.isBottom) b ⊔= AT + if (!pureValue.isBottom) b ⊔= AF + apply(bool = b) + def normalCompletion: Elem = + if (pureValue.isBottom) Bot + else + val res = AbsComp.Result(pureValue, AbsPureValue(CONST_EMPTY)) + Elem(comp = AbsComp(Map("normal" -> res))) + def abruptCompletion: Elem = apply(comp = comp.removeNormal) + + /** absent helpers */ + def removeAbsent: Elem = elem -- absentTop + def isAbsent: Elem = + var b: AbsBool = AbsBool.Bot + if (!absent.isBottom) b ⊔= AT + if (!removeAbsent.isBottom) b ⊔= AF + apply(bool = b) + + /** refine receiver object */ + def refineThis(func: Func): Elem = elem + + /** get syntactic SDO */ + def getSDO(method: String): List[(Func, Elem)] = ??? + + /** get lexical result */ + def getLexical(method: String): Elem = ??? + + /** getters */ + def comp: AbsComp = elem.comp + def pureValue: AbsPureValue = elem.pureValue + def clo: AbsClo = elem.pureValue.clo + def cont: AbsCont = elem.pureValue.cont + def part: AbsPart = elem.pureValue.part + def astValue: AbsAstValue = elem.pureValue.astValue + def nt: AbsNt = elem.pureValue.nt + def codeUnit: AbsCodeUnit = elem.pureValue.codeUnit + def const: AbsConst = elem.pureValue.const + def math: AbsMath = elem.pureValue.math + def simpleValue: AbsSimpleValue = elem.pureValue.simpleValue + def number: AbsNumber = elem.pureValue.number + def bigInt: AbsBigInt = elem.pureValue.bigInt + def str: AbsStr = elem.pureValue.str + def bool: AbsBool = elem.pureValue.bool + def undef: AbsUndef = elem.pureValue.undef + def nullv: AbsNull = elem.pureValue.nullv + def absent: AbsAbsent = elem.pureValue.absent + def ty: ValueTy = notSupported("value.BasicDomain.toTy") + + // ------------------------------------------------------------------------- + // private helpers + // ------------------------------------------------------------------------- + // conversion to integers + private def asInt: Set[Int] = + var set: Set[Int] = Set() + for (Math(n) <- elem.math if n.isValidInt) set += n.toInt + for (Number(n) <- elem.number if n.isValidInt) set += n.toInt + set + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/value/ValueDomain.scala b/src/main/scala/esmeta/analyzer/domain/value/ValueDomain.scala new file mode 100644 index 0000000000..234695db7c --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/value/ValueDomain.scala @@ -0,0 +1,205 @@ +package esmeta.analyzer.domain.value + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.cfg.Func +import esmeta.es.* +import esmeta.state.* +import esmeta.ty.* +import esmeta.ir.{COp, Name, VOp, MOp} +import esmeta.util.* + +trait ValueDomainDecl { self: Self => + + /** abstract valude domain */ + trait ValueDomain extends Domain[AValue] { + + /** abstraction functions for an original value */ + def apply(value: Value): Elem = alpha(AValue.from(value)) + + /** constructor with types */ + def apply(ty: Ty): Elem + + /** constructor for completions */ + def createCompletion( + ty: AbsValue, + value: AbsValue, + target: AbsValue, + ): Elem + + /** abstraction functions for raw data */ + def apply(ast: Ast): Elem = apply(AstValue(ast)) + def apply(n: Double): Elem = apply(Number(n)) + def apply(s: String): Elem = apply(Str(s)) + def apply(b: Boolean): Elem = apply(Bool(b)) + def apply(d: BigDecimal): Elem = apply(Math(d)) + + /** predefined top values */ + def compTop: Elem + def pureValueTop: Elem + def cloTop: Elem + def contTop: Elem + def partTop: Elem + def astValueTop: Elem + def ntTop: Elem + def codeUnitTop: Elem + def constTop: Elem + def mathTop: Elem + def simpleValueTop: Elem + def numberTop: Elem + def bigIntTop: Elem + def strTop: Elem + def boolTop: Elem + def undefTop: Elem + def nullTop: Elem + def absentTop: Elem + + /** constructors */ + def apply( + comp: AbsComp = AbsComp.Bot, + pureValue: AbsPureValue = AbsPureValue.Bot, + clo: AbsClo = AbsClo.Bot, + cont: AbsCont = AbsCont.Bot, + part: AbsPart = AbsPart.Bot, + astValue: AbsAstValue = AbsAstValue.Bot, + nt: AbsNt = AbsNt.Bot, + codeUnit: AbsCodeUnit = AbsCodeUnit.Bot, + const: AbsConst = AbsConst.Bot, + math: AbsMath = AbsMath.Bot, + simpleValue: AbsSimpleValue = AbsSimpleValue.Bot, + num: AbsNumber = AbsNumber.Bot, + bigInt: AbsBigInt = AbsBigInt.Bot, + str: AbsStr = AbsStr.Bot, + bool: AbsBool = AbsBool.Bot, + undef: AbsUndef = AbsUndef.Bot, + nullv: AbsNull = AbsNull.Bot, + absent: AbsAbsent = AbsAbsent.Bot, + ): Elem + + /** raw tuple of each simple value type */ + type RawTuple = ( + AbsComp, + AbsPureValue, + ) + + /** extractors */ + def unapply(elem: Elem): Option[RawTuple] + + /** transfer for variadic operation */ + def vopTransfer(vop: VOp, vs: List[Elem]): Elem + + /** transfer for mathematical operation */ + def mopTransfer(mop: MOp, vs: List[Elem]): Elem + + /** abstract value interfaces */ + extension (elem: Elem) { + + /** get key values */ + def keyValue: Elem + + /** bitwise operations */ + def &(that: Elem): Elem + def |(that: Elem): Elem + def ^(that: Elem): Elem + + /** comparison operations */ + def =^=(that: Elem): Elem + def ==^==(that: Elem): Elem + def <(that: Elem): Elem + + /** logical operations */ + def &&(that: Elem): Elem + def ||(that: Elem): Elem + def ^^(that: Elem): Elem + + /** numeric operations */ + def +(that: Elem): Elem + def sub(that: Elem): Elem + def /(that: Elem): Elem + def *(that: Elem): Elem + def %(that: Elem): Elem + def %%(that: Elem): Elem + def **(that: Elem): Elem + def <<(that: Elem): Elem + def >>>(that: Elem): Elem + def >>(that: Elem): Elem + + /** unary operations */ + def unary_- : Elem + def unary_! : Elem + def unary_~ : Elem + def abs: Elem + def floor: Elem + + /** type operations */ + def typeOf(st: AbsState): Elem + def typeCheck(tname: String, st: AbsState): Elem + + /** helper functions for abstract transfer */ + def convertTo(cop: COp, radix: Elem): Elem + def sourceText: Elem + def parse(rule: Elem): Elem + def duplicated(st: AbsState): Elem + def substring(from: Elem): Elem + def substring(from: Elem, to: Elem): Elem + def trim(leading: Boolean, trailing: Boolean): Elem + def clamp(lower: Elem, upper: Elem): Elem + def isArrayIndex: Elem + + /** prune abstract values */ + def pruneValue(r: Elem, positive: Boolean): Elem + def pruneField(field: String, r: Elem, positive: Boolean): Elem + def pruneType(r: Elem, positive: Boolean): Elem + def pruneTypeCheck(r: Elem, positive: Boolean): Elem + + /** single check */ + def isSingle: Boolean = elem.getSingle match + case One(_) => true + case _ => false + + /** get reachable address partitions */ + def reachableParts: Set[Part] + + /** completion helpers */ + def wrapCompletion: Elem + def unwrapCompletion: Elem + def isCompletion: Elem + def normalCompletion: Elem + def abruptCompletion: Elem + + /** absent helpers */ + def removeAbsent: Elem + def isAbsent: Elem + + /** refine receiver object */ + def refineThis(func: Func): Elem + + /** get syntactic SDO */ + def getSDO(method: String): List[(Func, Elem)] + + /** get lexical result */ + def getLexical(method: String): Elem + + /** getters */ + def comp: AbsComp + def pureValue: AbsPureValue + def clo: AbsClo + def cont: AbsCont + def part: AbsPart + def astValue: AbsAstValue + def nt: AbsNt + def codeUnit: AbsCodeUnit + def const: AbsConst + def math: AbsMath + def simpleValue: AbsSimpleValue + def number: AbsNumber + def bigInt: AbsBigInt + def str: AbsStr + def bool: AbsBool + def undef: AbsUndef + def nullv: AbsNull + def absent: AbsAbsent + def ty: ValueTy + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala b/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala new file mode 100644 index 0000000000..3517b95766 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala @@ -0,0 +1,662 @@ +package esmeta.analyzer.domain.value + +import esmeta.analyzer.* +import esmeta.analyzer.domain.* +import esmeta.cfg.Func +import esmeta.es.* +import esmeta.ir.{COp, Name, VOp, MOp} +import esmeta.parser.ESValueParser +import esmeta.state.* +import esmeta.spec.{Grammar => _, *} +import esmeta.ty.* +import esmeta.ty.util.{Stringifier => TyStringifier} +import esmeta.util.* +import esmeta.util.Appender.* +import esmeta.util.BaseUtils.* +import scala.annotation.tailrec +import scala.collection.mutable.{Map => MMap, Set => MSet} + +trait ValueTypeDomainDecl { self: Self => + + /** type domain for values */ + object ValueTypeDomain extends ValueDomain { + + /** elements */ + case class Elem(ty: ValueTy) extends Appendable + + /** top element */ + lazy val Top: Elem = exploded("top abstract value") + + /** bottom element */ + val Bot: Elem = Elem(ValueTy()) + + /** abstraction functions */ + def alpha(vs: Iterable[AValue]): Elem = Elem(getValueTy(vs)) + + /** constructor with types */ + def apply(ty: Ty): Elem = ty match + case _: UnknownTy => Bot + case vty: ValueTy => Elem(vty) + + /** constructor for completions */ + def createCompletion( + ty: AbsValue, + value: AbsValue, + target: AbsValue, + ): Elem = + val consts = ty.ty.const + val normal = + if (consts contains "normal") value.ty.pureValue + else PureValueTy() + val abrupt = consts -- Fin("normal") + Elem(ValueTy(normal = normal, abrupt = abrupt)) + + /** predefined top values */ + lazy val compTop: Elem = notSupported("value.TypeDomain.compTop") + lazy val pureValueTop: Elem = notSupported("value.TypeDomain.pureValueTop") + lazy val cloTop: Elem = Elem(CloT) + lazy val contTop: Elem = Elem(ContT) + lazy val partTop: Elem = notSupported("value.TypeDomain.partTop") + lazy val astValueTop: Elem = Elem(AstT) + lazy val ntTop: Elem = notSupported("value.TypeDomain.ntTop") + lazy val codeUnitTop: Elem = Elem(CodeUnitT) + lazy val constTop: Elem = notSupported("value.TypeDomain.constTop") + lazy val mathTop: Elem = Elem(MathT) + lazy val simpleValueTop: Elem = + notSupported("value.TypeDomain.simpleValueTop") + lazy val numberTop: Elem = Elem(NumberT) + lazy val bigIntTop: Elem = Elem(BigIntT) + lazy val strTop: Elem = Elem(StrT) + lazy val boolTop: Elem = Elem(BoolT) + lazy val undefTop: Elem = Elem(UndefT) + lazy val nullTop: Elem = Elem(NullT) + lazy val absentTop: Elem = Elem(AbsentT) + + /** constructors */ + def apply( + comp: AbsComp, + pureValue: AbsPureValue, + clo: AbsClo, + cont: AbsCont, + part: AbsPart, + astValue: AbsAstValue, + nt: AbsNt, + codeUnit: AbsCodeUnit, + const: AbsConst, + math: AbsMath, + simpleValue: AbsSimpleValue, + num: AbsNumber, + bigInt: AbsBigInt, + str: AbsStr, + bool: AbsBool, + undef: AbsUndef, + nullv: AbsNull, + absent: AbsAbsent, + ): Elem = Top + + /** extractors */ + def unapply(elem: Elem): Option[RawTuple] = None + + /** appender */ + given rule: Rule[Elem] = (app, elem) => + import TyStringifier.given + app >> elem.ty + + /** transfer for variadic operation */ + def vopTransfer(vop: VOp, vs: List[Elem]): Elem = vop match + case VOp.Min | VOp.Max => mathTop + case VOp.Concat => strTop + + /** transfer for mathematical operation */ + def mopTransfer(mop: MOp, vs: List[Elem]): Elem = mathTop + + /** element interfaces */ + extension (elem: Elem) { + + /** get key values */ + def keyValue: Elem = notSupported("value.TypeDomain.Elem.keyValue") + + /** partial order */ + def ⊑(that: Elem): Boolean = elem.ty <= that.ty + + /** join operator */ + def ⊔(that: Elem): Elem = Elem(elem.ty || that.ty) + + /** meet operator */ + override def ⊓(that: Elem): Elem = Elem(elem.ty && that.ty) + + /** prune operator */ + override def --(that: Elem): Elem = Elem(elem.ty -- that.ty) + + /** concretization function */ + override def gamma: BSet[AValue] = Inf + + /** get reachable address partitions */ + def reachableParts: Set[Part] = Set() + + /** bitwise operations */ + def &(that: Elem): Elem = bitwiseOp(elem, that) + def |(that: Elem): Elem = bitwiseOp(elem, that) + def ^(that: Elem): Elem = bitwiseOp(elem, that) + + /** comparison operations */ + def =^=(that: Elem): Elem = + (elem.getSingle, that.getSingle) match + case (Zero, _) | (_, Zero) => Bot + case (One(l), One(r)) => Elem(BoolT(l == r)) + case _ if (elem ⊓ that).isBottom => Elem(FalseT) + case _ => boolTop + def ==^==(that: Elem): Elem = numericCompareOP(elem, that) + def <(that: Elem): Elem = numericCompareOP(elem, that) + + /** logical operations */ + def &&(that: Elem): Elem = logicalOp(_ && _)(elem, that) + def ||(that: Elem): Elem = logicalOp(_ || _)(elem, that) + def ^^(that: Elem): Elem = logicalOp(_ ^ _)(elem, that) + + /** numeric operations */ + def +(that: Elem): Elem = numericOp(elem, that) + def sub(that: Elem): Elem = numericOp(elem, that) + def /(that: Elem): Elem = numericOp(elem, that) + def *(that: Elem): Elem = numericOp(elem, that) + def %(that: Elem): Elem = numericOp(elem, that) + def %%(that: Elem): Elem = numericOp(elem, that) + def **(that: Elem): Elem = numericOp(elem, that) + def <<(that: Elem): Elem = mathOp(elem, that) ⊔ bigIntOp(elem, that) + def >>(that: Elem): Elem = mathOp(elem, that) ⊔ bigIntOp(elem, that) + def >>>(that: Elem): Elem = mathOp(elem, that) + + /** unary operations */ + def unary_- : Elem = numericUnaryOp(elem) + def unary_! : Elem = logicalUnaryOp(!_)(elem) + def unary_~ : Elem = numericUnaryOp(elem) + def abs: Elem = mathTop + def floor: Elem = mathTop + + /** type operations */ + def typeOf(st: AbsState): Elem = + val ty = elem.ty + var names: Set[String] = Set() + if ( + ty.name.set match + case Inf => true + case Fin(set) => set.exists(cfg.tyModel.isSubTy(_, "Object")) + ) names += "Object" + if (ty.symbol) names += "Symbol" + if (!ty.number.isBottom) names += "Number" + if (ty.bigInt) names += "BigInt" + if (!ty.str.isBottom) names += "String" + if (!ty.bool.isBottom) names += "Boolean" + if (ty.undef) names += "Undefined" + if (ty.nullv) names += "Null" + Elem(StrT(names)) + + /** type check */ + def typeCheck(tname: String, st: AbsState): Elem = + val names = instanceNameSet(elem.ty) + if (names.isEmpty) Bot + else if (names == Set(tname)) Elem(TrueT) + else if (!names.contains(tname)) Elem(FalseT) + else boolTop + + /** helper functions for abstract transfer */ + def convertTo(cop: COp, radix: Elem): Elem = + val ty = elem.ty + Elem(cop match + case COp.ToApproxNumber if (!ty.math.isBottom) => + NumberT + case COp.ToNumber + if (!ty.math.isBottom || !ty.str.isBottom || !ty.number.isBottom) => + NumberT + case COp.ToBigInt + if (!ty.math.isBottom || !ty.str.isBottom || !ty.number.isBottom || ty.bigInt) => + BigIntT + case COp.ToMath + if (!ty.math.isBottom || !ty.number.isBottom || ty.bigInt) => + MathT + case COp.ToStr(_) + if (!ty.str.isBottom || !ty.number.isBottom || ty.bigInt) => + StrT + case _ => ValueTy(), + ) + def sourceText: Elem = strTop + def parse(rule: Elem): Elem = rule.ty.nt match + case Inf => exploded("too imprecise nt rule for parsing") + case Fin(set) => + Elem(ValueTy(astValue = AstNameTy((for { + nt <- set + name = nt.name + } yield name).toSet))) + def duplicated(st: AbsState): Elem = boolTop + def substring(from: Elem): Elem = strTop + def substring(from: Elem, to: Elem): Elem = strTop + def trim(leading: Boolean, trailing: Boolean): Elem = strTop + def clamp(lower: Elem, upper: Elem): Elem = mathTop + def isArrayIndex: Elem = boolTop + + /** prune abstract values */ + def pruneValue(r: Elem, positive: Boolean): Elem = + if (positive) elem ⊓ r + else if (r.isSingle) elem -- r + else elem + def pruneField(field: String, r: Elem, positive: Boolean): Elem = + field match + case "Value" => + val normal = ty.normal.prune(r.ty.pureValue, positive) + Elem(ty.copy(comp = CompTy(normal, ty.abrupt))) + case "Type" => + Elem(r.ty.const.getSingle match + case One("normal") => + if (positive) ValueTy(normal = ty.normal) + else ValueTy(abrupt = ty.abrupt) + case One(tname) => + if (positive) ValueTy(abrupt = Fin(tname)) + else + ValueTy(normal = ty.normal, abrupt = ty.abrupt -- Fin(tname)) + case _ => ty, + ) + case _ => elem + def pruneType(r: Elem, positive: Boolean): Elem = + r.ty.str.getSingle match + case One(tname) => + val that = Elem(tname match + case "Object" => NameT("Object") + case "Symbol" => SymbolT + case "Number" => NumberT + case "BigInt" => BigIntT + case "String" => StrT + case "Boolean" => BoolT + case "Undefined" => UndefT + case "Null" => NullT + case _ => ValueTy(), + ) + if (positive) elem ⊓ that else elem -- that + case _ => elem + def pruneTypeCheck(r: Elem, positive: Boolean): Elem = (for { + tname <- r.getSingle match + case One(Str(s)) => Some(s) + case One(Nt(n, _)) => Some(n) + case _ => None + if cfg.tyModel.infos.contains(tname) + } yield { + if (positive) Elem(NameT(tname)) + else elem -- Elem(NameT(tname)) + }).getOrElse(elem) + + /** completion helpers */ + def wrapCompletion: Elem = + val ty = elem.ty + Elem( + ValueTy( + normal = ty.normal || ty.pureValue, + abrupt = ty.abrupt, + ), + ) + def unwrapCompletion: Elem = + val ty = elem.ty + Elem(ValueTy(pureValue = ty.normal || ty.pureValue)) + def isCompletion: Elem = + val ty = elem.ty + var bs: Set[Boolean] = Set() + if (!ty.comp.isBottom) bs += true + if (!ty.pureValue.isBottom) bs += false + Elem(ValueTy(bool = BoolTy(bs))) + def normalCompletion: Elem = Elem(ValueTy(normal = elem.ty.pureValue)) + def abruptCompletion: Elem = Elem(ValueTy(abrupt = elem.ty.abrupt)) + + /** absent helpers */ + def removeAbsent: Elem = Elem(elem.ty -- AbsentT) + def isAbsent: Elem = + var bs: Set[Boolean] = Set() + if (elem.ty.absent) bs += true + if (!elem.removeAbsent.ty.isBottom) bs += false + Elem(BoolT(bs)) + + /** refine receiver object */ + def refineThis(func: Func): Elem = elem + + /** get lexical result */ + def getLexical(method: String): Elem = Elem( + if (elem.ty.astValue.isBottom) ValueTy() + else + method match + case "SV" | "TRV" | "StringValue" => StrT + case "IdentifierCodePoints" => StrT + case "MV" | "NumericValue" => NumberT || BigIntT + case "TV" => StrT // XXX ignore UndefT case + case "BodyText" | "FlagText" => StrT + case "Contains" => BoolT + case _ => ValueTy(), + ) + + /** get syntactic SDO */ + def getSDO(method: String): List[(Func, Elem)] = elem.ty.astValue match + case AstTopTy => + for { + func <- cfg.funcs if func.isSDO + List(_, newMethod) <- allSdoPattern.unapplySeq(func.name) + if newMethod == method + } yield (func, Elem(AstT)) + case AstNameTy(names) => + for { + name <- names.toList + pair <- astSdoCache((name, method)) + } yield pair + case AstSingleTy(name, idx, subIdx) => + synSdoCache((name, idx, subIdx, method)) + + /** getters */ + def comp: AbsComp = notSupported("value.TypeDomain.Elem.comp") + def pureValue: AbsPureValue = + notSupported("value.TypeDomain.Elem.pureValue") + def clo: AbsClo = ty.clo match + case Inf => AbsClo.Top + case Fin(set) => + AbsClo(for { + name <- set.toList + if cfg.fnameMap.contains(name) + } yield AClo(cfg.fnameMap(name), Map())) // TODO captured + def cont: AbsCont = ty.cont match + case Inf => AbsCont.Top + case Fin(set) => + AbsCont(for { + fid <- set.toList + node = cfg.nodeMap(fid) + func = cfg.funcOf(node) + } yield ACont(NodePoint(func, node, View()), Map())) // TODO captured + def part: AbsPart = notSupported("value.TypeDomain.Elem.part") + def astValue: AbsAstValue = notSupported("value.TypeDomain.Elem.astValue") + def nt: AbsNt = notSupported("value.TypeDomain.Elem.nt") + def codeUnit: AbsCodeUnit = notSupported("value.TypeDomain.Elem.codeUnit") + def const: AbsConst = notSupported("value.TypeDomain.Elem.const") + def math: AbsMath = notSupported("value.TypeDomain.Elem.math") + def simpleValue: AbsSimpleValue = + notSupported("value.TypeDomain.Elem.simpleValue") + def number: AbsNumber = notSupported("value.TypeDomain.Elem.number") + def bigInt: AbsBigInt = notSupported("value.TypeDomain.Elem.bigInt") + def str: AbsStr = notSupported("value.TypeDomain.Elem.str") + def bool: AbsBool = notSupported("value.TypeDomain.Elem.undef") + def undef: AbsUndef = notSupported("value.TypeDomain.Elem.nullv") + def nullv: AbsNull = notSupported("value.TypeDomain.Elem.absent") + def absent: AbsAbsent = notSupported("value.TypeDomain.Elem.ty") + def ty: ValueTy = elem.ty + } + + // ------------------------------------------------------------------------- + // private helpers + // ------------------------------------------------------------------------- + // value type getter + private def getValueTy(vs: Iterable[AValue]): ValueTy = + vs.foldLeft(ValueTy()) { case (vty, v) => vty || getValueTy(v) } + + // value type getter + private def getValueTy(v: AValue): ValueTy = v match + case AComp(CONST_NORMAL, v, _) => NormalT(getValueTy(v)) + case _: AComp => AbruptT + case AClo(func, _) => CloT(func.name) + case ACont(target, _) => ContT(target.node.id) + case AstValue(ast) => AstT(ast.name) + case nt: Nt => NtT(nt) + case CodeUnit(_) => CodeUnitT + case Const(name) => ConstT(name) + case Math(n) => MathT(n) + case n: Number => NumberT(n) + case BigInt(_) => BigIntT + case Str(n) => StrT(n) + case Bool(true) => TrueT + case Bool(false) => FalseT + case Undef => UndefT + case Null => NullT + case Absent => AbsentT + case v => notSupported(s"impossible to convert to pure type ($v)") + + // mathematical operator helper + private lazy val mathOp: (Elem, Elem) => Elem = (l, r) => + if (!l.ty.math.isBottom && !r.ty.math.isBottom) mathTop + else Bot + + // number operator helper + private lazy val numberOp: (Elem, Elem) => Elem = (l, r) => + if (!l.ty.number.isBottom && !r.ty.number.isBottom) numberTop + else Bot + + // big integer operator helper + private lazy val bigIntOp: (Elem, Elem) => Elem = (l, r) => + if (l.ty.bigInt && r.ty.bigInt) bigIntTop + else Bot + + // bitwise operator helper + private lazy val bitwiseOp: (Elem, Elem) => Elem = (l, r) => + mathOp(l, r) ⊔ bigIntOp(l, r) + + // logical unary operator helper + private def logicalUnaryOp( + op: Boolean => Boolean, + ): Elem => Elem = + b => Elem(ValueTy(bool = BoolTy(for (x <- b.ty.bool.set) yield op(x)))) + + // logical operator helper + private def logicalOp( + op: (Boolean, Boolean) => Boolean, + ): (Elem, Elem) => Elem = (l, r) => + Elem(ValueTy(bool = BoolTy(for { + x <- l.ty.bool.set + y <- r.ty.bool.set + } yield op(x, y)))) + + // numeric comparison operator helper + private lazy val numericCompareOP: (Elem, Elem) => Elem = (l, r) => + Elem( + ValueTy( + bool = BoolTy( + if ( + ( + (!l.ty.math.isBottom || !l.ty.number.isBottom) && + (!r.ty.math.isBottom || !r.ty.number.isBottom) + ) || (l.ty.bigInt && r.ty.bigInt) + ) Set(true, false) + else Set(), + ), + ), + ) + + // numeric unary operator helper + private lazy val numericUnaryOp: Elem => Elem = x => + var res: Elem = Bot + if (!x.ty.math.isBottom) res ⊔= mathTop + if (!x.ty.number.isBottom) res ⊔= numberTop + if (x.ty.bigInt) res ⊔= bigIntTop + res + + // numeric operator helper + private lazy val numericOp: (Elem, Elem) => Elem = (l, r) => + Elem( + ValueTy( + math = l.ty.math && r.ty.math, + number = l.ty.number && r.ty.number, + bigInt = l.ty.bigInt && r.ty.bigInt, + ), + ) + + /** instance name */ + private def instanceNameSet(ty: ValueTy): Set[String] = + var names: Set[String] = Set() + for (name <- ty.name.set) + names ++= cfg.tyModel.subTys.getOrElse(name, Set(name)) + names ++= ancestors(name) + if (!ty.astValue.isBottom) ty.astValue match + case AstTopTy => + names ++= astChildMap.keySet + "ParseNode" + "Nonterminal" + case ty: AstNonTopTy => + val astNames = ty.toName.names + names += "ParseNode" + for (astName <- astNames) + names ++= astChildMap.getOrElse(astName, Set(astName)) + names + + /** get ancestor types */ + private def ancestors(tname: String): Set[String] = + ancestorList(tname).toSet + private def ancestorList(tname: String): List[String] = + tname :: parent(tname).map(ancestorList).getOrElse(Nil) + + /** get parent types */ + private def parent(name: String): Option[String] = for { + TyInfo(parent, _, _) <- cfg.tyModel.infos.get(name) + p <- parent + } yield p + + /** ast type check helper */ + lazy val astDirectChildMap: Map[String, Set[String]] = + (cfg.grammar.prods.map { + case Production(lhs, _, _, rhsList) => + val name = lhs.name + val subs = rhsList.collect { + case Rhs(_, List(Nonterminal(name, _)), _) => name + }.toSet + name -> subs + }).toMap + lazy val astChildMap: Map[String, Set[String]] = + var descs = Map[String, Set[String]]() + def aux(name: String): Set[String] = descs.get(name) match + case Some(set) => set + case None => + val set = (for { + sub <- astDirectChildMap.getOrElse(name, Set()) + elem <- aux(sub) + } yield elem) + name + descs += name -> set + set + cfg.grammar.prods.foreach(prod => aux(prod.name)) + descs + + /** sdo access helper */ + private lazy val astSdoCache: ((String, String)) => List[(Func, Elem)] = + cached[(String, String), List[(Func, Elem)]] { + case (name, method) => + val result = (for { + (fid, thisTy, hint) <- sdoMap.getOrElse(name, Set()) + if hint == method + } yield (cfg.funcMap(fid), Elem(thisTy))).toList + if (result.isEmpty) { + if (defaultSdos contains method) { + val defaultFunc = cfg.fnameMap(s".$method") + for { + (rhs, idx) <- cfg.grammar.nameMap(name).rhsList.zipWithIndex + subIdx <- (0 until rhs.countSubs) + } yield (defaultFunc, Elem(AstSingleT(name, idx, subIdx))) + } else Nil + } else result + } + private lazy val synSdoCache = + cached[(String, Int, Int, String), List[(Func, Elem)]] { + case (name, idx, subIdx, method) => + val result = (for { + (fid, thisTy, hint) <- sdoMap.getOrElse( + s"$name[$idx,$subIdx]", + Set(), + ) + if hint == method + } yield (cfg.funcMap(fid), Elem(thisTy))).toList + if (result.isEmpty) { + if (defaultSdos contains method) { + val defaultFunc = cfg.fnameMap(s".$method") + List((defaultFunc, Elem(AstSingleT(name, idx, subIdx)))) + } else Nil + } else result + } + + /** sdo with default case */ + val defaultSdos = List( + "Contains", + "AllPrivateIdentifiersValid", + "ContainsArguments", + ) + + private lazy val allSdoPattern = """(|\w+\[\d+,\d+\])\.(\w+)""".r + private lazy val sdoPattern = """(\w+)\[(\d+),(\d+)\]\.(\w+)""".r + private lazy val sdoMap = { + val edges: MMap[String, MSet[String]] = MMap() + for { + prod <- cfg.grammar.prods + name = prod.name if !(cfg.grammar.lexicalNames contains name) + (rhs, idx) <- prod.rhsList.zipWithIndex + subIdx <- (0 until rhs.countSubs) + } { + val syntacticName = s"$name[$idx,$subIdx]" + edges += (syntacticName -> MSet(name)) + rhs.getNts(subIdx) match + case List(Some(chain)) => + if (edges contains chain) edges(chain) += syntacticName + else edges(chain) = MSet(syntacticName) + case _ => + } + val worklist = QueueWorklist[String](List()) + val infos: MMap[String, MSet[(Int, ValueTy, String)]] = MMap() + var defaultInfos: MMap[String, MSet[(Int, ValueTy, String)]] = MMap() + for { + func <- cfg.funcs if func.isSDO + isDefaultSdo = func.name.startsWith("") if !isDefaultSdo + List(name, idxStr, subIdxStr, method) <- sdoPattern.unapplySeq( + func.name, + ) + (idx, subIdx) = (idxStr.toInt, subIdxStr.toInt) + key = s"$name[$idx,$subIdx]" + } { + val newInfo = (func.id, AstSingleT(name, idx, subIdx), method) + val isDefaultSdo = defaultSdos contains method + + // update target info + val targetInfos = if (isDefaultSdo) defaultInfos else infos + if (targetInfos contains key) targetInfos(key) += newInfo + else targetInfos(key) = MSet(newInfo) + if (targetInfos contains name) targetInfos(name) += newInfo + else targetInfos(name) = MSet(newInfo) + + // propagate chain production + if (!isDefaultSdo) worklist += name + } + + // record original infos + val origInfos = (for { (k, set) <- infos } yield k -> set.toSet).toMap + + // propagate chain productions + @tailrec + def aux(): Unit = worklist.next match + case Some(key) => + val childInfo = infos.getOrElse(key, MSet()) + for { + next <- edges.getOrElse(key, MSet()) + info = infos.getOrElse(next, MSet()) + oldInfoSize = info.size + + newInfo = + // A[i,j] -> A + if (key endsWith "]") info ++ childInfo + // A.method -> B[i,j].method + // only if B[i,j].method not exists (chain production) + else { + val origInfo = origInfos.getOrElse(next, Set()) + info ++ (for { + triple <- childInfo + if !(origInfo.exists(_._3 == triple._3)) + } yield triple) + } + + _ = infos(next) = newInfo + if newInfo.size > oldInfoSize + } worklist += next + aux() + case None => /* do nothing */ + aux() + + // merge default infos + (for { + key <- infos.keySet ++ defaultInfos.keySet + info = infos.getOrElse(key, MSet()) + defaultInfo = defaultInfos.getOrElse(key, MSet()) + finalInfo = (info ++ defaultInfo).toSet + } yield key -> finalInfo).toMap + } + } +} diff --git a/src/main/scala/esmeta/analyzer/domain/value/package.scala b/src/main/scala/esmeta/analyzer/domain/value/package.scala new file mode 100644 index 0000000000..94ca5f3d41 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/domain/value/package.scala @@ -0,0 +1,12 @@ +package esmeta.analyzer.domain.value + +import esmeta.analyzer.{Analyzer, domain} + +type Self = Decl & domain.Decl & Analyzer + +trait Decl + extends ValueDomainDecl + with ValueBasicDomainDecl + with ValueTypeDomainDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/package.scala b/src/main/scala/esmeta/analyzer/package.scala index d0b82d6550..81cc026660 100644 --- a/src/main/scala/esmeta/analyzer/package.scala +++ b/src/main/scala/esmeta/analyzer/package.scala @@ -12,290 +12,12 @@ import esmeta.util.BaseUtils.* import esmeta.util.SystemUtils.* import java.io.PrintWriter -/** analyzer elements */ -trait AnalyzerElem { - override def toString: String = toString(true, false, false) - - /** stringify with options */ - def toString( - detail: Boolean = false, - line: Boolean = false, - asite: Boolean = false, - ): String = - val stringifier = AnalyzerElem.getStringifier(detail, line, asite) - import stringifier.elemRule - stringify(this) -} -object AnalyzerElem { - val getStringifier = cached[(Boolean, Boolean, Boolean), Stringifier] { - Stringifier(_, _, _) - } -} - /** exploded */ def exploded(msg: String): Nothing = throw AnalysisImprecise(msg) /** not supported */ def notSupported(msg: String): Nothing = throw NotSupported(msg) -// ----------------------------------------------------------------------------- -// global mutable options and structures -// ----------------------------------------------------------------------------- -/** control-flow graph (CFG) */ -def cfg: CFG = analyzer.cfg - -/** current static analyzer */ -def analyzer: Analyzer = get(globalAnalyzer, "Analyzer") -private var globalAnalyzer: Option[Analyzer] = None -def withAnalyzer[T](analyzer: Analyzer)(t: => T): T = - globalAnalyzer = Some(analyzer) - AbsState.setBase(new Initialize(analyzer.cfg)) - val res = t - globalAnalyzer = None - res - -/** abstract semantics */ -def sem: AbsSemantics = get(globalSem, "abstract semantics") -private var globalSem: Option[AbsSemantics] = None -def withSem[T](sem: AbsSemantics)(t: => T): T = - globalSem = Some(sem) - val res = t - globalSem = None - res - -/** logger */ -def warning(str: String): Unit = - val cpStr = sem.curCp.fold("")(" @ " + _.toString) - val msg = s"$str$cpStr" - if (!msgSet.contains(msg)) - msgSet += msg - warn(msg) - nf.map(nf => { nf.println(msg); nf.flush }) -private var nf: Option[PrintWriter] = None -private var msgSet: Set[String] = Set() -def withLog[T](filename: String)(t: => T): T = - nf = Some(getPrintWriter(filename)) - val res = t - msgSet = Set() - nf.map(_.close) - nf = None - res - -private def get[T](opt: Option[T], msg: String): T = - opt.getOrElse(throw Error(s"$msg is not yet initialized.")) - -/** analysis time limit */ -var TYPE_CHECK: Boolean = false - -/** analysis time limit */ -var TIME_LIMIT: Option[Long] = None - -/** debugging mode */ -var DEBUG: Boolean = false - -/** REPL mode */ -var USE_REPL: Boolean = false - -/** Run continue command at startup when using repl */ -var REPL_CONTINUE: Boolean = false - -/** check period */ -var CHECK_PERIOD: Int = 10000 - -/** IR sensitivity */ -var IR_SENS: Boolean = true - -/** throw exception for not yet compiled expressions */ -var YET_THROW: Boolean = false - -/** use condition-based refinement */ -var USE_REFINE: Boolean = false - -// ----------------------------------------------------------------------------- -// shortcuts -// ----------------------------------------------------------------------------- -lazy val T = Bool(true) -lazy val F = Bool(false) -lazy val AB = AbsBool.Top -lazy val AT = AbsBool(T) -lazy val AF = AbsBool(F) -lazy val AVT = AbsValue(T) -lazy val AVF = AbsValue(F) -lazy val AVB = AbsValue(T, F) -lazy val AV_TYPE = AbsValue(Str("Type")) -lazy val AV_VALUE = AbsValue(Str("Value")) -lazy val AV_TARGET = AbsValue(Str("Target")) - -// ----------------------------------------------------------------------------- -// abstract domains -// ----------------------------------------------------------------------------- -/** initialize analysis domains (only once) */ -def initDomain( - stateDomain: StateDomain = state.BasicDomain, - retDomain: RetDomain = ret.BasicDomain, - heapDomain: HeapDomain = heap.BasicDomain, - objDomain: ObjDomain = obj.BasicDomain, - valueDomain: ValueDomain = value.BasicDomain, - compDomain: CompDomain = comp.BasicDomain, - pureValueDomain: PureValueDomain = pureValue.BasicDomain, - cloDomain: CloDomain = clo.SetDomain(), - contDomain: ContDomain = cont.SetDomain(), - partDomain: PartDomain = part.SetDomain(), - astValueDomain: AstValueDomain = astValue.FlatDomain, - ntDomain: NtDomain = nt.FlatDomain, - mathDomain: MathDomain = math.FlatDomain, - codeUnitDomain: CodeUnitDomain = codeUnit.FlatDomain, - constDomain: ConstDomain = const.FlatDomain, - simpleValueDomain: SimpleValueDomain = simpleValue.BasicDomain, - numberDomain: NumberDomain = number.FlatDomain, - bigIntDomain: BigIntDomain = bigInt.FlatDomain, - strDomain: StrDomain = str.SetDomain(), - boolDomain: BoolDomain = bool.FlatDomain, - undefDomain: UndefDomain = undef.SimpleDomain, - nullDomain: NullDomain = nullv.SimpleDomain, - absentDomain: AbsentDomain = absent.SimpleDomain, -): Unit = - if (initialized) error("analysis configuration is already initialized") - _stateDomain = Some(stateDomain) - _retDomain = Some(retDomain) - _heapDomain = Some(heapDomain) - _objDomain = Some(objDomain) - _valueDomain = Some(valueDomain) - _compDomain = Some(compDomain) - _pureValueDomain = Some(pureValueDomain) - _cloDomain = Some(cloDomain) - _contDomain = Some(contDomain) - _partDomain = Some(partDomain) - _astValueDomain = Some(astValueDomain) - _ntDomain = Some(ntDomain) - _mathDomain = Some(mathDomain) - _codeUnitDomain = Some(codeUnitDomain) - _constDomain = Some(constDomain) - _simpleValueDomain = Some(simpleValueDomain) - _numberDomain = Some(numberDomain) - _bigIntDomain = Some(bigIntDomain) - _strDomain = Some(strDomain) - _boolDomain = Some(boolDomain) - _undefDomain = Some(undefDomain) - _nullDomain = Some(nullDomain) - _absentDomain = Some(absentDomain) - -// domain initialized -private var initialized = false - -// private domains -private var _stateDomain: Option[StateDomain] = None -private var _retDomain: Option[RetDomain] = None -private var _heapDomain: Option[HeapDomain] = None -private var _objDomain: Option[ObjDomain] = None -private var _valueDomain: Option[ValueDomain] = None -private var _compDomain: Option[CompDomain] = None -private var _pureValueDomain: Option[PureValueDomain] = None -private var _cloDomain: Option[CloDomain] = None -private var _contDomain: Option[ContDomain] = None -private var _partDomain: Option[PartDomain] = None -private var _astValueDomain: Option[AstValueDomain] = None -private var _ntDomain: Option[NtDomain] = None -private var _mathDomain: Option[MathDomain] = None -private var _codeUnitDomain: Option[CodeUnitDomain] = None -private var _constDomain: Option[ConstDomain] = None -private var _simpleValueDomain: Option[SimpleValueDomain] = None -private var _numberDomain: Option[NumberDomain] = None -private var _bigIntDomain: Option[BigIntDomain] = None -private var _strDomain: Option[StrDomain] = None -private var _boolDomain: Option[BoolDomain] = None -private var _undefDomain: Option[UndefDomain] = None -private var _nullDomain: Option[NullDomain] = None -private var _absentDomain: Option[AbsentDomain] = None - -type StateDomain = state.Domain -lazy val AbsState = _stateDomain.getOrElse(state.BasicDomain) -type AbsState = AbsState.Elem - -type RetDomain = ret.Domain -lazy val AbsRet = _retDomain.getOrElse(ret.BasicDomain) -type AbsRet = AbsRet.Elem - -type HeapDomain = heap.Domain -lazy val AbsHeap = _heapDomain.getOrElse(heap.BasicDomain) -type AbsHeap = AbsHeap.Elem - -type ObjDomain = obj.Domain -lazy val AbsObj = _objDomain.getOrElse(obj.BasicDomain) -type AbsObj = AbsObj.Elem - -type ValueDomain = value.Domain -lazy val AbsValue = _valueDomain.getOrElse(value.BasicDomain) -type AbsValue = AbsValue.Elem - -type CompDomain = comp.Domain -lazy val AbsComp = _compDomain.getOrElse(comp.BasicDomain) -type AbsComp = AbsComp.Elem - -type PureValueDomain = pureValue.Domain -lazy val AbsPureValue = _pureValueDomain.getOrElse(pureValue.BasicDomain) -type AbsPureValue = AbsPureValue.Elem - -type CloDomain = clo.Domain -lazy val AbsClo = _cloDomain.getOrElse(clo.SetDomain()) -type AbsClo = AbsClo.Elem - -type ContDomain = cont.Domain -lazy val AbsCont = _contDomain.getOrElse(cont.SetDomain()) -type AbsCont = AbsCont.Elem - -type PartDomain = part.Domain -lazy val AbsPart = _partDomain.getOrElse(part.SetDomain()) -type AbsPart = AbsPart.Elem - -type AstValueDomain = astValue.Domain -lazy val AbsAstValue = _astValueDomain.getOrElse(astValue.FlatDomain) -type AbsAstValue = AbsAstValue.Elem - -type NtDomain = nt.Domain -lazy val AbsNt = _ntDomain.getOrElse(nt.FlatDomain) -type AbsNt = AbsNt.Elem - -type MathDomain = math.Domain -lazy val AbsMath = _mathDomain.getOrElse(math.FlatDomain) -type AbsMath = AbsMath.Elem - -type CodeUnitDomain = codeUnit.Domain -lazy val AbsCodeUnit = _codeUnitDomain.getOrElse(codeUnit.FlatDomain) -type AbsCodeUnit = AbsCodeUnit.Elem - -type ConstDomain = const.Domain -lazy val AbsConst = _constDomain.getOrElse(const.FlatDomain) -type AbsConst = AbsConst.Elem - -type SimpleValueDomain = simpleValue.Domain -lazy val AbsSimpleValue = _simpleValueDomain.getOrElse(simpleValue.BasicDomain) -type AbsSimpleValue = AbsSimpleValue.Elem - -type NumberDomain = number.Domain -lazy val AbsNumber = _numberDomain.getOrElse(number.FlatDomain) -type AbsNumber = AbsNumber.Elem - -type BigIntDomain = bigInt.Domain -lazy val AbsBigInt = _bigIntDomain.getOrElse(bigInt.FlatDomain) -type AbsBigInt = AbsBigInt.Elem - -type StrDomain = str.Domain -lazy val AbsStr = _strDomain.getOrElse(str.SetDomain()) -type AbsStr = AbsStr.Elem - -type BoolDomain = bool.Domain -lazy val AbsBool = _boolDomain.getOrElse(bool.FlatDomain) -type AbsBool = AbsBool.Elem - -type UndefDomain = undef.Domain -lazy val AbsUndef = _undefDomain.getOrElse(undef.SimpleDomain) -type AbsUndef = AbsUndef.Elem - -type NullDomain = nullv.Domain -lazy val AbsNull = _nullDomain.getOrElse(nullv.SimpleDomain) -type AbsNull = AbsNull.Elem - -type AbsentDomain = absent.Domain -lazy val AbsAbsent = _absentDomain.getOrElse(absent.SimpleDomain) -type AbsAbsent = AbsAbsent.Elem +/** shortcurts */ +val T = Bool(true) +val F = Bool(false) diff --git a/src/main/scala/esmeta/analyzer/repl/REPL.scala b/src/main/scala/esmeta/analyzer/repl/REPL.scala index 496da63832..4d474fffa9 100644 --- a/src/main/scala/esmeta/analyzer/repl/REPL.scala +++ b/src/main/scala/esmeta/analyzer/repl/REPL.scala @@ -19,160 +19,163 @@ import scala.Console.* import scala.collection.mutable.ArrayBuffer import scala.util.matching.Regex -// REPL for static analysis -object REPL { - - // completer - private val completer: TreeCompleter = - TreeCompleter(Command.commands.map(optionNode(_)): _*) - private def optionNode(cmd: Command) = - node(cmd.name :: cmd.options.map(argNode(_)): _*) - private def argNode(opt: String) = - node(s"-$opt" :: getArgNodes(opt): _*) - private def getArgNodes(opt: String): List[TreeCompleter.Node] = opt match { - case CmdBreak.func => cfg.funcs.map(x => node(x.name)) - case CmdBreak.block => - (0 until cfg.nodeMap.size).map(x => node(x.toString)).toList - case CmdInfo.ret => cfg.funcs.map(x => node(x.name)) - case CmdInfo.block => - (0 until cfg.nodeMap.size).map(x => node(x.toString)).toList - case _ => Nil - } +trait ReplDecl { self: Self => + + // REPL for static analysis + object Repl { + + // completer + private val completer: TreeCompleter = + TreeCompleter(Command.commands.map(optionNode(_)): _*) + private def optionNode(cmd: Command) = + node(cmd.name :: cmd.options.map(argNode(_)): _*) + private def argNode(opt: String) = + node(s"-$opt" :: getArgNodes(opt): _*) + private def getArgNodes(opt: String): List[TreeCompleter.Node] = opt match { + case CmdBreak.func => cfg.funcs.map(x => node(x.name)) + case CmdBreak.block => + (0 until cfg.nodeMap.size).map(x => node(x.toString)).toList + case CmdInfo.ret => cfg.funcs.map(x => node(x.name)) + case CmdInfo.block => + (0 until cfg.nodeMap.size).map(x => node(x.toString)).toList + case _ => Nil + } - // get the number of iterations - inline def iter: Int = sem.iter - - // show current status - def showStatus(cp: Option[ControlPoint]): Unit = cp.map(showStatus) - def showStatus(cp: ControlPoint): Unit = println(s"[$iter] ${cpInfo(cp)}") - def cpInfo(cp: ControlPoint, detail: Boolean = false): String = - sem.getString(cp, CYAN, detail) - - // handle when the static analysis is finished - def finished: Unit = { - printlnColor(CYAN)(s"- Static analysis finished. (# iter: $iter)") - setCp(None) - continue = false - runDirect - } + // get the number of iterations + inline def iter: Int = sem.iter - // jline - private val terminal: Terminal = TerminalBuilder.builder().build() - private val reader: LineReader = LineReaderBuilder - .builder() - .terminal(terminal) - .completer(completer) - .build() - private val prompt: String = LINE_SEP + s"${MAGENTA}analyzer>${RESET} " - - // show help message at the first time - lazy val firstHelp: Unit = { CmdHelp.showHelp; println } - - // repl stop - private var replStop: Boolean = false - - // check whether skip REPL - def isSkip(cp: ControlPoint): Boolean = jumpTo match { - case _ if nextEntry => true - case _ if untilMerged => - if (sem.worklist.isEmpty && !merged) true - else { untilMerged = false; merged = false; false } - case Some(targetIter) => - if (iter < targetIter) true - else { jumpTo = None; false } - case _ => - continue && !isBreak(cp) && { - if (replStop) { replStop = false; false } - else true - } - } + // show current status + def showStatus(cp: Option[ControlPoint]): Unit = cp.map(showStatus) + def showStatus(cp: ControlPoint): Unit = println(s"[$iter] ${cpInfo(cp)}") + def cpInfo(cp: ControlPoint, detail: Boolean = false): String = + sem.getString(cp, CYAN, detail) - // run REPL - def apply(transfer: AbsTransfer, cp: ControlPoint): Unit = try { - if (!isSkip(cp)) { - setCp(Some(cp)) + // handle when the static analysis is finished + def finished: Unit = { + printlnColor(CYAN)(s"- Static analysis finished. (# iter: $iter)") + setCp(None) continue = false runDirect } - transfer(cp) - } catch { - case e: ESMetaError => - printlnColor(RED)(s"- # iter: $iter") - showStatus(cp) - throw e - case e: Throwable => - printlnColor(RED)(s"- unexpectedly terminated (# iter: $iter).") - showStatus(cp) - throw e - } - def runDirect: Unit = try { - firstHelp - showStatus(curCp) - while ({ - reader.readLine(prompt) match { - case null => stop - case line => - line.split("\\s+").toList match { - case Nil | List("") => - continue = false - false - case name :: args => { - Command.cmdMap.get(name) match { - case Some(cmd) => cmd(curCp, args) - case None => - println(s"The command `$name` does not exist. (Try `help`)") + + // jline + private val terminal: Terminal = TerminalBuilder.builder().build() + private val reader: LineReader = LineReaderBuilder + .builder() + .terminal(terminal) + .completer(completer) + .build() + private val prompt: String = LINE_SEP + s"${MAGENTA}analyzer>${RESET} " + + // show help message at the first time + lazy val firstHelp: Unit = { CmdHelp.showHelp; println } + + // repl stop + private var replStop: Boolean = false + + // check whether skip REPL + def isSkip(cp: ControlPoint): Boolean = jumpTo match { + case _ if nextEntry => true + case _ if untilMerged => + if (sem.worklist.isEmpty && !merged) true + else { untilMerged = false; merged = false; false } + case Some(targetIter) => + if (iter < targetIter) true + else { jumpTo = None; false } + case _ => + continue && !isBreak(cp) && { + if (replStop) { replStop = false; false } + else true + } + } + + // run REPL + def apply(transfer: AbsTransfer, cp: ControlPoint): Unit = try { + if (!isSkip(cp)) { + setCp(Some(cp)) + continue = false + runDirect + } + transfer(cp) + } catch { + case e: ESMetaError => + printlnColor(RED)(s"- # iter: $iter") + showStatus(cp) + throw e + case e: Throwable => + printlnColor(RED)(s"- unexpectedly terminated (# iter: $iter).") + showStatus(cp) + throw e + } + def runDirect: Unit = try { + firstHelp + showStatus(curCp) + while ({ + reader.readLine(prompt) match { + case null => stop + case line => + line.split("\\s+").toList match { + case Nil | List("") => + continue = false + false + case name :: args => { + Command.cmdMap.get(name) match { + case Some(cmd) => cmd(curCp, args) + case None => + println(s"The command `$name` does not exist. (Try `help`)") + } + !continue } - !continue } - } - } - }) {} - } catch { case e: EndOfFileException => error("stop for debugging") } + } + }) {} + } catch { case e: EndOfFileException => error("stop for debugging") } - // original control point - private var origCp: Option[ControlPoint] = None + // original control point + private var origCp: Option[ControlPoint] = None - // current control point - var curCp: Option[ControlPoint] = None + // current control point + var curCp: Option[ControlPoint] = None - // set current control point - def setCp(cpOpt: Option[ControlPoint]) = { - origCp = cpOpt - curCp = cpOpt - } - def moveCp(cp: ControlPoint) = curCp = Some(cp) - def restoreCp() = curCp = origCp - - // continue option - var continue: Boolean = REPL_CONTINUE - - // jump point - var jumpTo: Option[Int] = None - - // jump to the next ECMAScript entry - var nextEntry: Boolean = false - - // jump to when the analysis result is merged - var untilMerged: Boolean = false - var merged: Boolean = false - - // break points - val breakpoints = ArrayBuffer[(String, String)]() - private def isBreak(cp: ControlPoint): Boolean = cp match { - case NodePoint(func, node, _) if func.entry == node => - breakpoints.exists { - case (CmdBreak.func, name) => name == cfg.funcOf(node).name - case (CmdBreak.block, uid) => uid.toInt == node.id - case _ => false - } - case NodePoint(_, node, _) => - breakpoints.exists { - case (CmdBreak.block, uid) => uid.toInt == node.id - case _ => false - } - case _ => false - } + // set current control point + def setCp(cpOpt: Option[ControlPoint]) = { + origCp = cpOpt + curCp = cpOpt + } + def moveCp(cp: ControlPoint) = curCp = Some(cp) + def restoreCp() = curCp = origCp + + // continue option + var continue: Boolean = replContinue + + // jump point + var jumpTo: Option[Int] = None + + // jump to the next ECMAScript entry + var nextEntry: Boolean = false + + // jump to when the analysis result is merged + var untilMerged: Boolean = false + var merged: Boolean = false + + // break points + val breakpoints = ArrayBuffer[(String, String)]() + private def isBreak(cp: ControlPoint): Boolean = cp match { + case NodePoint(func, node, _) if func.entry == node => + breakpoints.exists { + case (CmdBreak.func, name) => name == cfg.funcOf(node).name + case (CmdBreak.block, uid) => uid.toInt == node.id + case _ => false + } + case NodePoint(_, node, _) => + breakpoints.exists { + case (CmdBreak.block, uid) => uid.toInt == node.id + case _ => false + } + case _ => false + } - // stop - def stop: Boolean = { breakpoints.clear(); continue = true; false } + // stop + def stop: Boolean = { breakpoints.clear(); continue = true; false } + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdBreak.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdBreak.scala index 6de6fd5603..545feb3c71 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdBreak.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdBreak.scala @@ -3,22 +3,26 @@ package esmeta.analyzer.repl.command import esmeta.analyzer.* import esmeta.analyzer.repl.* -// break command -case object CmdBreak - extends Command( - "break", - "Add a break point.", - ) { - // options - val options @ List(func, block) = List("func", "block") +trait CmdBreakDecl { self: Self => - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = args match { - case opt :: bp :: _ if options contains opt.substring(1) => - REPL.breakpoints += (opt.substring(1) -> bp) - case _ => println("Inappropriate argument") + /** break command */ + case object CmdBreak + extends Command( + "break", + "Add a break point.", + ) { + + /** options */ + val options @ List(func, block) = List("func", "block") + + /** run command */ + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = args match { + case opt :: bp :: _ if options contains opt.substring(1) => + Repl.breakpoints += (opt.substring(1) -> bp) + case _ => println("Inappropriate argument") + } } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdContinue.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdContinue.scala index 5c86a362c5..e4d4fc5675 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdContinue.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdContinue.scala @@ -3,18 +3,21 @@ package esmeta.analyzer.repl.command import esmeta.analyzer.* import esmeta.analyzer.repl.* +trait CmdContinueDecl { self: Self => + // continue command -case object CmdContinue - extends Command( - "continue", - "Continue static analysis.", - ) { - // options - val options: List[String] = Nil + case object CmdContinue + extends Command( + "continue", + "Continue static analysis.", + ) { + // options + val options: List[String] = Nil - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = REPL.continue = true + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = Repl.continue = true + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdEntry.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdEntry.scala index d7fdd7dd28..a79cbf9414 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdEntry.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdEntry.scala @@ -5,72 +5,75 @@ import esmeta.analyzer.repl.* import esmeta.util.QueueWorklist import scala.annotation.tailrec +trait CmdEntryDecl { self: Self => + // entry command -case object CmdEntry - extends Command( - "entry", - "Show the set of entry functions of current function", - ) { - // options - val options @ List(path, graph) = List("path", "graph") + case object CmdEntry + extends Command( + "entry", + "Show the set of entry functions of current function", + ) { + // options + val options @ List(path, graph) = List("path", "graph") - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = notYetCmd - // TODO - // cpOpt.map(cp => { - // var paths = Map[ControlPoint, Path]() - // var visited = Set[ControlPoint]() - // val worklist = QueueWorklist[Path](List(Nil)) + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = notYetCmd + // TODO + // cpOpt.map(cp => { + // var paths = Map[ControlPoint, Path]() + // var visited = Set[ControlPoint]() + // val worklist = QueueWorklist[Path](List(Nil)) - // @tailrec - // def aux: Unit = worklist.next match { - // case Some(path) => { - // val curCp = path.headOption.getOrElse(cp) - // val func = sem.funcOf(curCp) - // val view = curCp.view - // val rp = ReturnPoint(func, view) - // visited += curCp - // func.headOption match { - // case Some(head: SyntaxDirectedHead) if head.withParams.isEmpty => - // if (!(paths contains curCp)) paths += curCp -> path.reverse - // case _ => for { - // (nextCp, _) <- sem.getRetEdges(rp) - // if !(visited contains nextCp) - // } { - // worklist += nextCp :: path - // } - // } - // aux - // } - // case _ => - // } - // aux + // @tailrec + // def aux: Unit = worklist.next match { + // case Some(path) => { + // val curCp = path.headOption.getOrElse(cp) + // val func = sem.funcOf(curCp) + // val view = curCp.view + // val rp = ReturnPoint(func, view) + // visited += curCp + // func.headOption match { + // case Some(head: SyntaxDirectedHead) if head.withParams.isEmpty => + // if (!(paths contains curCp)) paths += curCp -> path.reverse + // case _ => for { + // (nextCp, _) <- sem.getRetEdges(rp) + // if !(visited contains nextCp) + // } { + // worklist += nextCp :: path + // } + // } + // aux + // } + // case _ => + // } + // aux - // // options - // args match { - // case "-path" :: _ => for { - // (_, path) <- paths - // len = path.length - // _ = println(s"[LENGTH = $len]") - // np <- path - // func = sem.funcOf(np) - // name = func.name - // } println(s" <- $name:$np") - // case "-graph" :: _ => { - // val (topCp, shortest) = paths.toList.minBy(_._2.length) - // val func = sem.funcOf(topCp) - // val name = func.name - // println(s"- entry with the shortest path: $name:$topCp") - // dumpCFG(Some(cp), path = Some(shortest)) - // } - // case _ => for { - // (topCp, _) <- paths - // f = sem.funcOf(topCp) - // name = f.name - // } println(name) - // } - // }) + // // options + // args match { + // case "-path" :: _ => for { + // (_, path) <- paths + // len = path.length + // _ = println(s"[LENGTH = $len]") + // np <- path + // func = sem.funcOf(np) + // name = func.name + // } println(s" <- $name:$np") + // case "-graph" :: _ => { + // val (topCp, shortest) = paths.toList.minBy(_._2.length) + // val func = sem.funcOf(topCp) + // val name = func.name + // println(s"- entry with the shortest path: $name:$topCp") + // dumpCFG(Some(cp), path = Some(shortest)) + // } + // case _ => for { + // (topCp, _) <- paths + // f = sem.funcOf(topCp) + // name = f.name + // } println(name) + // } + // }) + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdExit.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdExit.scala index 61c00c8356..056f553d1c 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdExit.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdExit.scala @@ -4,18 +4,21 @@ import esmeta.analyzer.* import esmeta.analyzer.repl.* import esmeta.util.BaseUtils.* +trait CmdExitDecl { self: Self => + // exit command -case object CmdExit - extends Command( - "exit", - "Exit the type checking.", - ) { - // options - val options = Nil + case object CmdExit + extends Command( + "exit", + "Exit the type checking.", + ) { + // options + val options = Nil - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = error("stop for debugging") + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = error("stop for debugging") + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdFindMerged.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdFindMerged.scala index f0aee1cabc..bd063ed508 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdFindMerged.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdFindMerged.scala @@ -5,24 +5,27 @@ import esmeta.analyzer.repl.* import esmeta.cfg.* import esmeta.util.BaseUtils.* +trait CmdFindMergedDecl { self: Self => + // find-merged command -case object CmdFindMerged - extends Command( - "find-merged", - "Find merged analysis results.", - ) { - // options - val options = Nil + case object CmdFindMerged + extends Command( + "find-merged", + "Find merged analysis results.", + ) { + // options + val options = Nil - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = cpOpt.map(cp => { - val st = cp match { - case np: NodePoint[Node] => sem(np) - case rp: ReturnPoint => sem(rp).state - } - st.findMerged - }) + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = cpOpt.map(cp => { + val st = cp match { + case np: NodePoint[Node] => sem(np) + case rp: ReturnPoint => sem(rp).state + } + st.findMerged + }) + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdGraph.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdGraph.scala index 47d1db5ab5..c4211c647e 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdGraph.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdGraph.scala @@ -6,24 +6,27 @@ import esmeta.analyzer.repl.* import esmeta.util.BaseUtils.* import esmeta.util.SystemUtils.* +trait CmdGraphDecl { self: Self => + // graph command -case object CmdGraph - extends Command( - "graph", - "Dump the current control graph.", - ) { - import DotPrinter.* + case object CmdGraph + extends Command( + "graph", + "Dump the current control graph.", + ) { + import DotPrinter.* - // options - val options @ List(total) = List("total") + // options + val options @ List(total) = List("total") - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = (optional(args.head.toInt), args) match - case (Some(depth), _) => dumpCFG(cpOpt, depth = Some(depth)) - case (None, s"-$total" :: _) => - dumpCFG(cpOpt, depth = None) - case _ => dumpCFG(cpOpt, depth = Some(0)) + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = (optional(args.head.toInt), args) match + case (Some(depth), _) => dumpCFG(cpOpt, depth = Some(depth)) + case (None, s"-$total" :: _) => + dumpCFG(cpOpt, depth = None) + case _ => dumpCFG(cpOpt, depth = Some(0)) + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdHelp.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdHelp.scala index b46dc9d1f6..fac21655de 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdHelp.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdHelp.scala @@ -3,27 +3,30 @@ package esmeta.analyzer.repl.command import esmeta.analyzer.* import esmeta.analyzer.repl.* +trait CmdHelpDecl { self: Self => + // help command -case object CmdHelp - extends Command( - "help", - "Show help message.", - ) { - // options - val options = Nil + case object CmdHelp + extends Command( + "help", + "Show help message.", + ) { + // options + val options = Nil - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = showHelp + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = showHelp - // show help message - def showHelp: Unit = { - println - println("command list:") - for (cmd <- Command.commands) { - println("- %-25s%s".format(cmd.name, cmd.help)) + // show help message + def showHelp: Unit = { + println + println("command list:") + for (cmd <- Command.commands) { + println("- %-25s%s".format(cmd.name, cmd.help)) + } } } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdInfo.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdInfo.scala index f57295bbf8..2c13d66945 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdInfo.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdInfo.scala @@ -4,51 +4,54 @@ import esmeta.analyzer.* import esmeta.analyzer.repl.* import esmeta.util.BaseUtils.* -// info command -case object CmdInfo - extends Command( - "info", - "Show abstract state of node", - ) { - // options - val options @ List(ret, block, detail, callsite) = - List("ret", "block", "detail", "callsite") +trait CmdInfoDecl { self: Self => - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = args match { - case opt :: optArgs if options contains opt.substring(1) => - showInfo(opt.substring(1), optArgs) - case _ => - cpOpt match { - case Some(cp) => - val detail = args.headOption == Some("-detail") - println(REPL.cpInfo(cp, detail)) - println - case None => - showInfo(ret, List("RunJobs")) - } - } +// info command + case object CmdInfo + extends Command( + "info", + "Show abstract state of node", + ) { + // options + val options @ List(ret, block, detail, callsite) = + List("ret", "block", "detail", "callsite") - // show information - def showInfo(opt: String, optArgs: List[String]): Unit = { - val info = (opt, optArgs) match { - case (`ret`, target :: _) => - val fname = target - sem.rpMap.keySet.filter(_.func.name == fname) - case (`block`, target :: _) if optional(target.toInt) != None => - val uid = target.toInt - sem.npMap.keySet.filter(_.node.id == uid) - case (`callsite`, _) => - val cp = REPL.curCp.get - val rp = ReturnPoint(cp.func, cp.view) - sem.retEdges(rp) + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = args match { + case opt :: optArgs if options contains opt.substring(1) => + showInfo(opt.substring(1), optArgs) case _ => - println("Inappropriate argument") - Set() + cpOpt match { + case Some(cp) => + val detail = args.headOption == Some("-detail") + println(Repl.cpInfo(cp, detail)) + println + case None => + showInfo(ret, List("RunJobs")) + } + } + + // show information + def showInfo(opt: String, optArgs: List[String]): Unit = { + val info = (opt, optArgs) match { + case (`ret`, target :: _) => + val fname = target + sem.rpMap.keySet.filter(_.func.name == fname) + case (`block`, target :: _) if optional(target.toInt) != None => + val uid = target.toInt + sem.npMap.keySet.filter(_.node.id == uid) + case (`callsite`, _) => + val cp = Repl.curCp.get + val rp = ReturnPoint(cp.func, cp.view) + sem.retEdges(rp) + case _ => + println("Inappropriate argument") + Set() + } + info.foreach(cp => { println(Repl.cpInfo(cp, true)); println }) } - info.foreach(cp => { println(REPL.cpInfo(cp, true)); println }) } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdJump.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdJump.scala index 7206342ce4..ef31a2a8ce 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdJump.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdJump.scala @@ -4,28 +4,31 @@ import esmeta.analyzer.* import esmeta.analyzer.repl.* import esmeta.util.BaseUtils.* +trait CmdJumpDecl { self: Self => + // jump command -case object CmdJump - extends Command( - "jump", - "Jump to a specific iteration.", - ) { - // options - val options @ List(entry, merged) = List("entry", "merged") + case object CmdJump + extends Command( + "jump", + "Jump to a specific iteration.", + ) { + // options + val options @ List(entry, merged) = List("entry", "merged") - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = args match { - case s"-${`entry`}" :: _ => - REPL.nextEntry = true; REPL.continue = true - case s"-${`merged`}" :: _ => - REPL.untilMerged = true; REPL.continue = true - case arg :: _ if !optional(arg.toInt).isEmpty => - val iter = arg.toInt - if (iter > REPL.iter) { REPL.jumpTo = Some(iter); REPL.continue = true } - else println(s"The iteration [$iter] is already passed.") - case _ => println("Inappropriate argument") + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = args match { + case s"-${`entry`}" :: _ => + Repl.nextEntry = true; Repl.continue = true + case s"-${`merged`}" :: _ => + Repl.untilMerged = true; Repl.continue = true + case arg :: _ if !optional(arg.toInt).isEmpty => + val iter = arg.toInt + if (iter > Repl.iter) { Repl.jumpTo = Some(iter); Repl.continue = true } + else println(s"The iteration [$iter] is already passed.") + case _ => println("Inappropriate argument") + } } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdListBreak.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdListBreak.scala index 85be388937..68063c1748 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdListBreak.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdListBreak.scala @@ -3,20 +3,23 @@ package esmeta.analyzer.repl.command import esmeta.analyzer.* import esmeta.analyzer.repl.* +trait CmdListBreakDecl { self: Self => + // list-break command -case object CmdListBreak - extends Command( - "list-break", - "Show the list of break points.", - ) { - // options - val options: List[String] = Nil + case object CmdListBreak + extends Command( + "list-break", + "Show the list of break points.", + ) { + // options + val options: List[String] = Nil - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = for { - ((k, v), i) <- REPL.breakpoints.zipWithIndex - } println(f"$i: $k%-15s $v") + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = for { + ((k, v), i) <- Repl.breakpoints.zipWithIndex + } println(f"$i: $k%-15s $v") + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdLog.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdLog.scala index 8dcd77f20d..e888b2a337 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdLog.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdLog.scala @@ -3,18 +3,21 @@ package esmeta.analyzer.repl.command import esmeta.analyzer.* import esmeta.analyzer.repl.* +trait CmdLogDecl { self: Self => + // log command -case object CmdLog - extends Command( - "log", - "Dump the state.", - ) { - // options - val options = Nil + case object CmdLog + extends Command( + "log", + "Dump the state.", + ) { + // options + val options = Nil - // TODO run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = notYetCmd + // TODO run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = notYetCmd + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdMove.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdMove.scala index 17de43e33a..55d6751250 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdMove.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdMove.scala @@ -6,53 +6,56 @@ import esmeta.analyzer.repl.* import esmeta.cfg.* import scala.Console.* -// Move command -case object CmdMove - extends Command( - "move", - "Move to specified control point.", - ) { - // options - val options @ List(to, reset) = List("to", "reset") +trait CmdMoveDecl { self: Self => - // get node points of given nid - def getNps(nid: Int): Array[NodePoint[Node]] = { - val node = cfg.nodeMap(nid) - sem.npMap.keys.toArray.filter(_.node.id == node.id) - } +// Move command + case object CmdMove + extends Command( + "move", + "Move to specified control point.", + ) { + // options + val options @ List(to, reset) = List("to", "reset") - // print node points - def printNps(nps: Array[NodePoint[Node]]) = { - nps.zipWithIndex.foreach { - case (np, idx) => - val npStr = np.toString(detail = true) - println(s" $idx: $npStr") + // get node points of given nid + def getNps(nid: Int): Array[NodePoint[Node]] = { + val node = cfg.nodeMap(nid) + sem.npMap.keys.toArray.filter(_.node.id == node.id) } - } - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = args match { - case s"-${`to`}" :: nid :: idx :: _ => - val nps = getNps(nid.toInt) - val i = idx.toInt - if (i < nps.size) { - val newNp = nps(i) - REPL.moveCp(newNp) - println(s"Current control point is moved to $newNp") - } else { - println(s"Wrong index: $i") - printNps(nps) + // print node points + def printNps(nps: Array[NodePoint[Node]]) = { + nps.zipWithIndex.foreach { + case (np, idx) => + val npStr = np.toString(detail = true) + println(s" $idx: $npStr") } + } + + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = args match { + case s"-${`to`}" :: nid :: idx :: _ => + val nps = getNps(nid.toInt) + val i = idx.toInt + if (i < nps.size) { + val newNp = nps(i) + Repl.moveCp(newNp) + println(s"Current control point is moved to $newNp") + } else { + println(s"Wrong index: $i") + printNps(nps) + } - case s"-${`to`}" :: nid :: Nil => - val nps = getNps(nid.toInt) - printNps(nps) - case s"-${`reset`}" :: _ => - REPL.restoreCp() - println("Current control point is restored") - case _ => println("Inappropriate argument") + case s"-${`to`}" :: nid :: Nil => + val nps = getNps(nid.toInt) + printNps(nps) + case s"-${`reset`}" :: _ => + Repl.restoreCp() + println("Current control point is restored") + case _ => println("Inappropriate argument") + } } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdPrint.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdPrint.scala index bd7745b343..487ac459a3 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdPrint.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdPrint.scala @@ -7,44 +7,48 @@ import esmeta.cfg.* import esmeta.ir.Expr import esmeta.util.BaseUtils.* +trait CmdPrintDecl { self: Self => + // print command -case object CmdPrint - extends Command( - "print", - "Print specific information", - ) { - // options - val options @ List(reachLoc, ret, expr) = List("reach-loc", "return", "expr") + case object CmdPrint + extends Command( + "print", + "Print specific information", + ) { + // options + val options @ List(reachLoc, ret, expr) = + List("reach-loc", "return", "expr") - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = { - val cp = cpOpt.getOrElse(sem.runJobsRp) - args match { - case s"-${`reachLoc`}" :: _ => { - val st = sem.getState(cp) - st.reachableParts.foreach(println _) - } - case s"-${`ret`}" :: _ => { - val v = cp match { - case np: NodePoint[Node] => println("no return value") - case rp: ReturnPoint => - val ret = sem(rp) - println(ret.state.getString(ret.value)) + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = { + val cp = cpOpt.getOrElse(sem.runJobsRp) + args match { + case s"-${`reachLoc`}" :: _ => { + val st = sem.getState(cp) + st.reachableParts.foreach(println _) } - } - case s"-${`expr`}" :: rest => { - val str = rest.mkString(" ") - val v = analyzer.transfer(cp, Expr.from(str)) - val st = cp match { - case np: NodePoint[Node] => sem(np) - case rp: ReturnPoint => sem(rp).state + case s"-${`ret`}" :: _ => { + val v = cp match { + case np: NodePoint[Node] => println("no return value") + case rp: ReturnPoint => + val ret = sem(rp) + println(ret.state.getString(ret.value)) + } + } + case s"-${`expr`}" :: rest => { + val str = rest.mkString(" ") + val v = transfer(cp, Expr.from(str)) + val st = cp match { + case np: NodePoint[Node] => sem(np) + case rp: ReturnPoint => sem(rp).state + } + println(st.getString(v)) } - println(st.getString(v)) + case _ => println("Inappropriate argument") } - case _ => println("Inappropriate argument") } } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdRmBreak.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdRmBreak.scala index e515b079e1..34eb4e798a 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdRmBreak.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdRmBreak.scala @@ -4,28 +4,31 @@ import esmeta.analyzer.* import esmeta.analyzer.repl.* import esmeta.util.BaseUtils.* +trait CmdRmBreakDecl { self: Self => + // rm-break command -case object CmdRmBreak - extends Command( - "rm-break", - "Remove a break point.", - ) { - // options - val options @ List(all) = List("all") + case object CmdRmBreak + extends Command( + "rm-break", + "Remove a break point.", + ) { + // options + val options @ List(all) = List("all") - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = args match { - case Nil => println("need arguments") - case arg :: _ => { - val breakpoints = REPL.breakpoints - optional(arg.toInt) match { - case _ if arg == s"-$all" => breakpoints.clear - case Some(idx) if idx.toInt < breakpoints.size => - breakpoints.remove(idx.toInt) - case _ => println("Inappropriate argument") + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = args match { + case Nil => println("need arguments") + case arg :: _ => { + val breakpoints = Repl.breakpoints + optional(arg.toInt) match { + case _ if arg == s"-$all" => breakpoints.clear + case Some(idx) if idx.toInt < breakpoints.size => + breakpoints.remove(idx.toInt) + case _ => println("Inappropriate argument") + } } } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdStop.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdStop.scala index fe55b315fb..b700a5c620 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdStop.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdStop.scala @@ -4,18 +4,21 @@ import esmeta.analyzer.* import esmeta.analyzer.repl.* import esmeta.util.BaseUtils.* +trait CmdStopDecl { self: Self => + // stop command -case object CmdStop - extends Command( - "stop", - "Stop the repl.", - ) { - // options - val options = Nil + case object CmdStop + extends Command( + "stop", + "Stop the repl.", + ) { + // options + val options = Nil - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = REPL.stop + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = Repl.stop + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/CmdWorklist.scala b/src/main/scala/esmeta/analyzer/repl/command/CmdWorklist.scala index 9d9bf05e1e..4cd7eec04f 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/CmdWorklist.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/CmdWorklist.scala @@ -3,26 +3,29 @@ package esmeta.analyzer.repl.command import esmeta.analyzer.* import esmeta.analyzer.repl.* +trait CmdWorklistDecl { self: Self => + // worklist command -case object CmdWorklist - extends Command( - "worklist", - "Show all the control points in the worklist", - ) { - // options - val options @ List(detail) = List("detail") + case object CmdWorklist + extends Command( + "worklist", + "Show all the control points in the worklist", + ) { + // options + val options @ List(detail) = List("detail") - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit = { - val worklist = sem.worklist - val size = worklist.size - println(s"Total $size elements exist in the worklist.") - args match { - case s"-${`detail`}" :: _ => sem.worklist.foreach(println(_)) - case _ => + // run command + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit = { + val worklist = sem.worklist + val size = worklist.size + println(s"Total $size elements exist in the worklist.") + args match { + case s"-${`detail`}" :: _ => sem.worklist.foreach(println(_)) + case _ => + } } } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/Command.scala b/src/main/scala/esmeta/analyzer/repl/command/Command.scala index 0ec0bd2305..238c38eb08 100644 --- a/src/main/scala/esmeta/analyzer/repl/command/Command.scala +++ b/src/main/scala/esmeta/analyzer/repl/command/Command.scala @@ -3,47 +3,51 @@ package esmeta.analyzer.repl.command import esmeta.analyzer.* import esmeta.analyzer.repl.* -// commands -abstract class Command( - // command name - val name: String, +trait CommandDecl { self: Self => - // command help message - val help: String = "", -) { - // options - val options: List[String] + /** commands */ + abstract class Command( + /** command name */ + val name: String, + /** command help message */ + val help: String = "", + ) { - // run command - def apply( - cpOpt: Option[ControlPoint], - args: List[String], - ): Unit + /** options */ + val options: List[String] - // not yet supported message - def notYetCmd: Unit = - notYet("this command is not yet supported") - def notYet(msg: String): Unit = - println(s"[NotSupported] $msg @ $name") -} -object Command { - val commands: List[Command] = List( - CmdHelp, - CmdContinue, - CmdMove, - CmdBreak, - CmdListBreak, - CmdRmBreak, - CmdJump, - CmdPrint, - CmdLog, - CmdGraph, - CmdExit, - CmdStop, - CmdInfo, - CmdEntry, - CmdWorklist, - CmdFindMerged, - ) - val cmdMap: Map[String, Command] = commands.map(cmd => (cmd.name, cmd)).toMap + /** run command */ + def apply( + cpOpt: Option[ControlPoint], + args: List[String], + ): Unit + + /** not yet supported message */ + def notYetCmd: Unit = + notYet("this command is not yet supported") + def notYet(msg: String): Unit = + println(s"[NotSupported] $msg @ $name") + } + object Command { + val commands: List[Command] = List( + CmdHelp, + CmdContinue, + CmdMove, + CmdBreak, + CmdListBreak, + CmdRmBreak, + CmdJump, + CmdPrint, + CmdLog, + CmdGraph, + CmdExit, + CmdStop, + CmdInfo, + CmdEntry, + CmdWorklist, + CmdFindMerged, + ) + val cmdMap: Map[String, Command] = + commands.map(cmd => (cmd.name, cmd)).toMap + } } diff --git a/src/main/scala/esmeta/analyzer/repl/command/package.scala b/src/main/scala/esmeta/analyzer/repl/command/package.scala new file mode 100644 index 0000000000..442c917031 --- /dev/null +++ b/src/main/scala/esmeta/analyzer/repl/command/package.scala @@ -0,0 +1,26 @@ +package esmeta.analyzer.repl.command + +import esmeta.analyzer.{Analyzer, repl} + +type Self = Decl & repl.Decl & Analyzer + +trait Decl + extends CommandDecl + with CmdBreakDecl + with CmdContinueDecl + with CmdEntryDecl + with CmdExitDecl + with CmdFindMergedDecl + with CmdGraphDecl + with CmdHelpDecl + with CmdInfoDecl + with CmdJumpDecl + with CmdListBreakDecl + with CmdLogDecl + with CmdMoveDecl + with CmdPrintDecl + with CmdRmBreakDecl + with CmdStopDecl + with CmdWorklistDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/analyzer/repl/package.scala b/src/main/scala/esmeta/analyzer/repl/package.scala new file mode 100644 index 0000000000..9f5d29d76c --- /dev/null +++ b/src/main/scala/esmeta/analyzer/repl/package.scala @@ -0,0 +1,7 @@ +package esmeta.analyzer.repl + +import esmeta.analyzer.Analyzer + +type Self = Decl & Analyzer + +trait Decl extends ReplDecl with command.Decl { self: Self => } diff --git a/src/main/scala/esmeta/analyzer/util/DotPrinter.scala b/src/main/scala/esmeta/analyzer/util/DotPrinter.scala index 8641efac1b..079892d407 100644 --- a/src/main/scala/esmeta/analyzer/util/DotPrinter.scala +++ b/src/main/scala/esmeta/analyzer/util/DotPrinter.scala @@ -9,86 +9,91 @@ import esmeta.util.SystemUtils.* import esmeta.util.Appender import scala.Console.* -class DotPrinter( - cp: ControlPoint, - cur: Option[ControlPoint] = None, -) extends CFGDotPrinter(cp.func) { - val WORKLIST = """"gray"""" +trait DotPrinterDecl { self: Self => - // helpers for views - lazy val view = sem.getEntryView(cp.view) - lazy val viewStr: String = norm(view) - lazy val rp: ReturnPoint = ReturnPoint(func, view) - def getNp[T <: Node](node: T): NodePoint[T] = - NodePoint(cfg.funcOf(node), node, view) + class DotPrinter( + cp: ControlPoint, + cur: Option[ControlPoint] = None, + ) extends CFGDotPrinter(cp.func) { + val WORKLIST = """"gray"""" - // helpers for functions - lazy val func = cp.func - override lazy val funcId: String = s"cluster${func.id}_$viewStr" - override lazy val funcName: String = - norm(func.headString) + (if (view.isEmpty) "" else s" [VIEW: $viewStr]") + // helpers for views + lazy val view = sem.getEntryView(cp.view) + lazy val viewStr: String = norm(view) + lazy val rp: ReturnPoint = ReturnPoint(func, view) + def getNp[T <: Node](node: T): NodePoint[T] = + NodePoint(cfg.funcOf(node), node, view) - // helpers for function exit - override lazy val exitColor: String = - if (sem.reachable(rp)) REACH else NON_REACH - override lazy val exitBgColor: String = - if (cur == Some(rp)) CURRENT - else if (sem.worklist has rp) WORKLIST - else NORMAL - override def exitEdgeColor(from: Node): String = - if (sem.reachable(getNp(from)) && sem.reachable(rp)) REACH - else NON_REACH + // helpers for functions + lazy val func = cp.func + override lazy val funcId: String = s"cluster${func.id}_$viewStr" + override lazy val funcName: String = + norm(func.headString) + (if (view.isEmpty) "" else s" [VIEW: $viewStr]") - // helpers for nodes - override def getId(node: Node): String = s"node${node.id}_$viewStr" - override def getColor(node: Node): String = - if (sem.reachable(getNp(node))) REACH else NON_REACH - override def getBgColor(node: Node): String = - val np = getNp(node) - if (cur == Some(np)) CURRENT - else if (sem.worklist has np) WORKLIST - else NORMAL - override def getEdgeColor(from: Node, to: Node): String = - if (sem.reachable(getNp(from)) && sem.reachable(getNp(to))) REACH - else NON_REACH + // helpers for function exit + override lazy val exitColor: String = + if (sem.reachable(rp)) REACH else NON_REACH + override lazy val exitBgColor: String = + if (cur == Some(rp)) CURRENT + else if (sem.worklist has rp) WORKLIST + else NORMAL + override def exitEdgeColor(from: Node): String = + if (sem.reachable(getNp(from)) && sem.reachable(rp)) REACH + else NON_REACH - // normalize strings for view - private val normPattern = """[-:\[\](),\s~?"]""".r - protected def norm(view: View): String = - normPattern.replaceAllIn(view.toString, "_") -} -object DotPrinter { - // path for CFG - val CFG_PATH = s"$ANALYZE_LOG_DIR/cfg" + // helpers for nodes + override def getId(node: Node): String = s"node${node.id}_$viewStr" + override def getColor(node: Node): String = + if (sem.reachable(getNp(node))) REACH else NON_REACH + override def getBgColor(node: Node): String = + val np = getNp(node) + if (cur == Some(np)) CURRENT + else if (sem.worklist has np) WORKLIST + else NORMAL + override def getEdgeColor(from: Node, to: Node): String = + if (sem.reachable(getNp(from)) && sem.reachable(getNp(to))) REACH + else NON_REACH - // dump CFG in DOT/PDF format - def dumpCFG( - cp: Option[ControlPoint] = None, - pdf: Boolean = true, - depth: Option[Int] = None, - path: Option[Path] = None, - ): Unit = try { - dumpDot(Graph(sem, cp, depth, path).toDot, pdf) - } catch { - case _: Throwable => printlnColor(RED)(s"Cannot dump CFG") + // normalize strings for view + private val normPattern = """[-:\[\](),\s~?"]""".r + protected def norm(view: View): String = + normPattern.replaceAllIn(view.toString, "_") } + object DotPrinter { + // path for CFG + val CFG_PATH = s"$ANALYZE_LOG_DIR/cfg" - // dump CFG function in a DOT/PDF format - def dumpFunc( - func: Func, - pdf: Boolean = true, - ): Unit = try { - dumpDot(func.toDot(), pdf) - } catch { - case _: Throwable => printlnColor(RED)(s"Cannot dump CFG function") - } + // dump CFG in DOT/PDF format + def dumpCFG( + cp: Option[ControlPoint] = None, + pdf: Boolean = true, + depth: Option[Int] = None, + path: Option[Path] = None, + ): Unit = try { + dumpDot(Graph(sem, cp, depth, path).toDot, pdf) + } catch { + case _: Throwable => printlnColor(RED)(s"Cannot dump CFG") + } - // dump DOT - def dumpDot(dot: String, pdf: Boolean): Unit = - dumpFile(dot, s"$CFG_PATH.dot") - if (pdf) { - executeCmd(s"""unflatten -l 10 -o ${CFG_PATH}_trans.dot $CFG_PATH.dot""") - executeCmd(s"""dot -Tpdf "${CFG_PATH}_trans.dot" -o "$CFG_PATH.pdf"""") - println(s"Dumped CFG to $CFG_PATH.pdf") - } else println(s"Dumped CFG to $CFG_PATH.dot") + // dump CFG function in a DOT/PDF format + def dumpFunc( + func: Func, + pdf: Boolean = true, + ): Unit = try { + dumpDot(func.toDot(), pdf) + } catch { + case _: Throwable => printlnColor(RED)(s"Cannot dump CFG function") + } + + // dump DOT + def dumpDot(dot: String, pdf: Boolean): Unit = + dumpFile(dot, s"$CFG_PATH.dot") + if (pdf) { + executeCmd( + s"""unflatten -l 10 -o ${CFG_PATH}_trans.dot $CFG_PATH.dot""", + ) + executeCmd(s"""dot -Tpdf "${CFG_PATH}_trans.dot" -o "$CFG_PATH.pdf"""") + println(s"Dumped CFG to $CFG_PATH.pdf") + } else println(s"Dumped CFG to $CFG_PATH.dot") + } } diff --git a/src/main/scala/esmeta/analyzer/util/Graph.scala b/src/main/scala/esmeta/analyzer/util/Graph.scala index a0ae7c6302..79b7d41788 100644 --- a/src/main/scala/esmeta/analyzer/util/Graph.scala +++ b/src/main/scala/esmeta/analyzer/util/Graph.scala @@ -4,82 +4,87 @@ import esmeta.analyzer.* import esmeta.cfg.* import esmeta.util.Appender -class Graph( - sem: AbsSemantics, - cur: Option[ControlPoint], - depthOpt: Option[Int], - pathOpt: Option[Path], -) { +trait GraphDecl { self: Self => - /** conversion to a DOT format */ - lazy val toDot: String = - given app: Appender = new Appender - (app >> "digraph").wrap { - app :> """graph [fontname = "Consolas"]""" - app :> """node [fontname = "Consolas"]""" - app :> """edge [fontname = "Consolas"]""" - (cur, depthOpt, pathOpt) match - case (Some(cp), _, Some(path)) => - var calleePrinter = DotPrinter(cp, cur) - calleePrinter.addTo(app) - for (callerNp <- path) - val callerPrinter = DotPrinter(callerNp) - callerPrinter.addTo(app) - drawCall(callerPrinter, callerNp.node, calleePrinter) - calleePrinter = callerPrinter - case (Some(cp), Some(depth), _) => - var printer = DotPrinter(cp, cur) - printer.addTo(app) - showPrev(printer, depth) - case _ => - var visited = Set[ReturnPoint]() - for { - (calleeRp, callerNps) <- sem.retEdges - callerNp <- callerNps - calleePrinter = DotPrinter(calleeRp, cur) - callerPrinter = DotPrinter(callerNp, cur) - callerRp = callerPrinter.rp - } { - if (!visited.contains(calleeRp)) - visited += calleeRp - calleePrinter.addTo(app) - if (!visited.contains(callerRp)) - visited += callerRp + class Graph( + sem: AbsSemantics, + cur: Option[ControlPoint], + depthOpt: Option[Int], + pathOpt: Option[Path], + ) { + + /** conversion to a DOT format */ + lazy val toDot: String = + given app: Appender = new Appender + (app >> "digraph").wrap { + app :> """graph [fontname = "Consolas"]""" + app :> """node [fontname = "Consolas"]""" + app :> """edge [fontname = "Consolas"]""" + (cur, depthOpt, pathOpt) match + case (Some(cp), _, Some(path)) => + var calleePrinter = DotPrinter(cp, cur) + calleePrinter.addTo(app) + for (callerNp <- path) + val callerPrinter = DotPrinter(callerNp) callerPrinter.addTo(app) - drawCall(callerPrinter, callerNp.node, calleePrinter) - } - } - app.toString + drawCall(callerPrinter, callerNp.node, calleePrinter) + calleePrinter = callerPrinter + case (Some(cp), Some(depth), _) => + var printer = DotPrinter(cp, cur) + printer.addTo(app) + showPrev(printer, depth) + case _ => + var visited = Set[ReturnPoint]() + for { + (calleeRp, callerNps) <- sem.retEdges + callerNp <- callerNps + calleePrinter = DotPrinter(calleeRp, cur) + callerPrinter = DotPrinter(callerNp, cur) + callerRp = callerPrinter.rp + } { + if (!visited.contains(calleeRp)) + visited += calleeRp + calleePrinter.addTo(app) + if (!visited.contains(callerRp)) + visited += callerRp + callerPrinter.addTo(app) + drawCall(callerPrinter, callerNp.node, calleePrinter) + } + } + app.toString - // show previous traces with depth - def showPrev( - printer: DotPrinter, - depth: Int, - )(using app: Appender): Unit = - var visited = Set[ReturnPoint](printer.rp) - def aux(calleePrinter: DotPrinter, depth: Int): Unit = if (depth > 0) { - val calleeRp = calleePrinter.rp - for (callerNp @ NodePoint(_, call, callView) <- sem.getRetEdges(calleeRp)) - val callerPrinter = DotPrinter(callerNp) - val callerRp = callerPrinter.rp - if (!visited.contains(callerRp)) - visited += callerRp - callerPrinter.addTo(app) - aux(callerPrinter, depth - 1) - drawCall(callerPrinter, callerNp.node, calleePrinter) - } - aux(printer, depth) + // show previous traces with depth + def showPrev( + printer: DotPrinter, + depth: Int, + )(using app: Appender): Unit = + var visited = Set[ReturnPoint](printer.rp) + def aux(calleePrinter: DotPrinter, depth: Int): Unit = if (depth > 0) { + val calleeRp = calleePrinter.rp + for ( + callerNp @ NodePoint(_, call, callView) <- sem.getRetEdges(calleeRp) + ) + val callerPrinter = DotPrinter(callerNp) + val callerRp = callerPrinter.rp + if (!visited.contains(callerRp)) + visited += callerRp + callerPrinter.addTo(app) + aux(callerPrinter, depth - 1) + drawCall(callerPrinter, callerNp.node, calleePrinter) + } + aux(printer, depth) - // draw call edges - def drawCall( - callerPrinter: DotPrinter, - call: Call, - calleePrinter: DotPrinter, - )(using app: Appender): Unit = - val cid = callerPrinter.getId(call) - val eid = calleePrinter.entryId - app :> s"$cid -> $eid [label=call]" -} + // draw call edges + def drawCall( + callerPrinter: DotPrinter, + call: Call, + calleePrinter: DotPrinter, + )(using app: Appender): Unit = + val cid = callerPrinter.getId(call) + val eid = calleePrinter.entryId + app :> s"$cid -> $eid [label=call]" + } -// path type -type Path = List[NodePoint[Call]] + // path type + type Path = List[NodePoint[Call]] +} diff --git a/src/main/scala/esmeta/analyzer/util/Stringifier.scala b/src/main/scala/esmeta/analyzer/util/Stringifier.scala index 527a84005f..43746d614c 100644 --- a/src/main/scala/esmeta/analyzer/util/Stringifier.scala +++ b/src/main/scala/esmeta/analyzer/util/Stringifier.scala @@ -12,147 +12,142 @@ import esmeta.util.* import esmeta.util.Appender.* import esmeta.util.BaseUtils.* -/** stringifier for analyzer */ -class Stringifier( - detail: Boolean, - location: Boolean, - asite: Boolean, -) { - private val cfgStringifier = CFGElem.getStringifier(detail, location) - import cfgStringifier.given +trait StringifierDecl { self: Self => - private val irStringifier = IRElem.getStringifier(detail, location) - import irStringifier.given + /** stringifier for analyzer */ + class Stringifier( + detail: Boolean, + location: Boolean, + asite: Boolean, + ) { + private val cfgStringifier = CFGElem.getStringifier(detail, location) + import cfgStringifier.given - import TyStringifier.given + private val irStringifier = IRElem.getStringifier(detail, location) + import irStringifier.given - /** elements */ - given elemRule: Rule[AnalyzerElem] = (app, elem) => - elem match - case elem: View => viewRule(app, elem) - case elem: AnalysisPoint => apRule(app, elem) - case elem: AValue => avRule(app, elem) - case elem: TypeMismatch => mismatchRule(app, elem) - // TODO case ty: Type => typeRule(app, ty) + import TyStringifier.given - /** view */ - given viewRule: Rule[View] = (app, view) => - def ctxtStr( - calls: List[String], - loops: List[LoopCtxt], - ): Appender = if (detail) { - app >> calls.mkString("[call: ", ", ", "]") - app >> loops - .map { case LoopCtxt(loop, depth) => s"${loop.id}($depth)" } - .mkString("[loop: ", ", ", "]") - } else { - app >> "[call: " >> calls.length >> "]" - app >> "[loop: " >> loops.length >> "]" - } + /** elements */ + given elemRule: Rule[AnalyzerElem] = (app, elem) => + elem match + case elem: View => viewRule(app, elem) + case elem: AnalysisPoint => apRule(app, elem) + case elem: AValue => avRule(app, elem) + case elem: TypeMismatch => mismatchRule(app, elem) + // TODO case ty: Type => typeRule(app, ty) - // ir contexts - if (IR_SENS) ctxtStr(view.calls.map(_.id.toString), view.loops) - // TODO type contexts - // if (TYPE_SENS) { - // given Rule[Iterable[Type]] = iterableRule("[", ", ", "]") - // app >> view.tys - // } - app + /** view */ + given viewRule: Rule[View] = (app, view) => + def auxRule[T]( + name: String, + )(using Rule[T]): Rule[Iterable[T]] = (app, iter) => + if (iter.isEmpty) app + else if (detail) iterableRule(s"[$name: ", ", ", "]")(app, iter) + else app >> s"[$name: " >> iter.size >> "]" + given cRule: Rule[Call] = (app, call) => app >> call.id + given csRule: Rule[Iterable[Call]] = auxRule("call") + given lRule: Rule[LoopCtxt] = { + case (app, LoopCtxt(loop, depth)) => app >> s"${loop.id}($depth)" + } + given lsRule: Rule[Iterable[LoopCtxt]] = auxRule("loop") + app >> view.calls >> view.loops - // analysis points - given apRule: Rule[AnalysisPoint] = (app, ap) => - given Rule[IRElem with LangEdge] = addLocRule - ap match - case cp: ControlPoint => cpRule(app, cp) - case CallPoint(callerNp, calleeNp) => - app >> "function call from " - app >> callerNp.func.name >> callerNp.node.callInst - app >> " to " >> calleeNp.func.name - case aap @ ArgAssignPoint(cp, idx) => - val param = aap.param - app >> "argument assignment to " - app >> (idx + 1).toOrdinal >> " parameter _" >> param.lhs.name >> "_" - app >> " when " >> cp - case InternalReturnPoint(irReturn, calleeRp) => - app >> "return statement in " >> calleeRp.func.name >> irReturn - case ReturnIfAbruptPoint(riap, riaExpr) => - app >> "returnIfAbrupt" - app >> "(" >> (if (riaExpr.check) "?" else "!") >> ") " - app >> "in " >> riap.func.name >> riaExpr + // analysis points + given apRule: Rule[AnalysisPoint] = (app, ap) => + given Rule[IRElem with LangEdge] = addLocRule + ap match + case cp: ControlPoint => cpRule(app, cp) + case CallPoint(callerNp, calleeNp) => + app >> "function call from " + app >> callerNp.func.name >> callerNp.node.callInst + app >> " to " >> calleeNp.func.name + case aap @ ArgAssignPoint(cp, idx) => + val param = aap.param + app >> "argument assignment to " + app >> (idx + 1).toOrdinal >> " parameter _" >> param.lhs.name >> "_" + app >> " when " >> cp + case InternalReturnPoint(irReturn, calleeRp) => + app >> "return statement in " >> calleeRp.func.name >> irReturn + case ReturnIfAbruptPoint(riap, riaExpr) => + app >> "returnIfAbrupt" + app >> "(" >> (if (riaExpr.check) "?" else "!") >> ") " + app >> "in " >> riap.func.name >> riaExpr - // control points - given cpRule: Rule[ControlPoint] = (app, cp) => - app >> cp.func.name >> "[" >> cp.func.id >> "]:" - app >> (cp match - case NodePoint(_, node, view) => node.simpleString - case ReturnPoint(func, view) => "RETURN" - ) - if (cp.view.isEmpty) app - else app >> ":" >> cp.view + // control points + given cpRule: Rule[ControlPoint] = (app, cp) => + app >> cp.func.name >> "[" >> cp.func.id >> "]:" + app >> (cp match + case NodePoint(_, node, view) => node.simpleString + case ReturnPoint(func, view) => "RETURN" + ) + if (cp.view.isEmpty) app + else app >> ":" >> cp.view - // values for analysis - given avRule: Rule[AValue] = (app, av) => - av match - case AComp(Const("normal"), v, _) => - app >> "N(" >> v >> ")" - case AComp(ty, value, target) => - app >> "comp[" >> ty - app >> "/" >> target.getOrElse(CONST_EMPTY.toString) >> "]" - app >> "(" >> value >> ")" - case Named(name) => app >> "#" >> name - case AllocSite(k, view) => app >> "#" >> k >> ":" >> view - case SubMap(baseLoc) => app >> baseLoc >> ":SubMap" - case AClo(func, _) => - app >> "clo<" >> func.irFunc.name >> ">" - case ACont(target, _) => - app >> "cont<" >> target >> ">" - case AstValue(ast) => - app >> f"☊[${ast.name}]<${ast.idx}> @ 0x${ast.hashCode}%08x" - case Nt(name, params) => - given Rule[Boolean] = (app, bool) => app >> (if (bool) "T" else "F") - given Rule[List[Boolean]] = iterableRule() - app >> "nt<" >> name - if (!params.isEmpty) app >> "[" >> params >> "]" - app >> ">" - case Math(n) => app >> n - case Const(name) => app >> "~" >> name >> "~" - case CodeUnit(c) => app >> c.toInt >> "cu" - case sv: SimpleValue => app >> sv.toString + // values for analysis + given avRule: Rule[AValue] = (app, av) => + av match + case AComp(Const("normal"), v, _) => + app >> "N(" >> v >> ")" + case AComp(ty, value, target) => + app >> "comp[" >> ty + app >> "/" >> target.getOrElse(CONST_EMPTY.toString) >> "]" + app >> "(" >> value >> ")" + case Named(name) => app >> "#" >> name + case AllocSite(k, view) => app >> "#" >> k >> ":" >> view + case SubMap(baseLoc) => app >> baseLoc >> ":SubMap" + case AClo(func, _) => + app >> "clo<" >> func.irFunc.name >> ">" + case ACont(target, _) => + app >> "cont<" >> target >> ">" + case AstValue(ast) => + app >> f"☊[${ast.name}]<${ast.idx}> @ 0x${ast.hashCode}%08x" + case Nt(name, params) => + given Rule[Boolean] = (app, bool) => app >> (if (bool) "T" else "F") + given Rule[List[Boolean]] = iterableRule() + app >> "nt<" >> name + if (!params.isEmpty) app >> "[" >> params >> "]" + app >> ">" + case Math(n) => app >> n + case Const(name) => app >> "~" >> name >> "~" + case CodeUnit(c) => app >> c.toInt >> "cu" + case sv: SimpleValue => app >> sv.toString - // specification type mismatches - given mismatchRule: Rule[TypeMismatch] = (app, mismatch) => - mismatch match - case ParamTypeMismatch(aap, actual) => - app >> "[ParamTypeMismatch] " >> aap - app :> "- expected: " >> aap.param.ty - app :> "- actual : " >> actual - case ReturnTypeMismatch(irp, actual) => - app >> "[ReturnTypeMismatch] " >> irp - app :> "- expected: " >> irp.calleeRp.func.retTy - app :> "- actual : " >> actual - case ArityMismatch(cp, actual) => - given Rule[(Int, Int)] = arityRangeRule - app >> "[ArityMismatch] " >> cp - app :> "- expected: " >> cp.func.arity - app :> "- actual : " >> actual - case UncheckedAbruptCompletionMismatch(riap, actual) => - app >> "[UncheckedAbruptCompletionMismatch] " >> riap - app :> "- actual : " >> actual + // specification type mismatches + given mismatchRule: Rule[TypeMismatch] = (app, mismatch) => + mismatch match + case ParamTypeMismatch(aap, actual) => + app >> "[ParamTypeMismatch] " >> aap + app :> "- expected: " >> aap.param.ty + app :> "- actual : " >> actual + case ReturnTypeMismatch(irp, actual) => + app >> "[ReturnTypeMismatch] " >> irp + app :> "- expected: " >> irp.calleeRp.func.retTy + app :> "- actual : " >> actual + case ArityMismatch(cp, actual) => + given Rule[(Int, Int)] = arityRangeRule + app >> "[ArityMismatch] " >> cp + app :> "- expected: " >> cp.func.arity + app :> "- actual : " >> actual + case UncheckedAbruptCompletionMismatch(riap, actual) => + app >> "[UncheckedAbruptCompletionMismatch] " >> riap + app :> "- actual : " >> actual - private val addLocRule: Rule[IRElem with LangEdge] = (app, elem) => { - for { - lang <- elem.langOpt - loc <- lang.loc - } app >> " " >> loc.toString - app - } + private val addLocRule: Rule[IRElem with LangEdge] = (app, elem) => { + for { + lang <- elem.langOpt + loc <- lang.loc + } app >> " " >> loc.toString + app + } - private val arityRangeRule: Rule[(Int, Int)] = { - case (app, (from, to)) => - if (from == to) app >> from - else app >> "[" >> from >> ", " >> to >> "]" - } + private val arityRangeRule: Rule[(Int, Int)] = { + case (app, (from, to)) => + if (from == to) app >> from + else app >> "[" >> from >> ", " >> to >> "]" + } - // TODO type - // given typeRule: Rule[Type] = (app, ty) => ??? + // TODO type + // given typeRule: Rule[Type] = (app, ty) => ??? + } } diff --git a/src/main/scala/esmeta/analyzer/util/package.scala b/src/main/scala/esmeta/analyzer/util/package.scala new file mode 100644 index 0000000000..7b2a28b9bf --- /dev/null +++ b/src/main/scala/esmeta/analyzer/util/package.scala @@ -0,0 +1,9 @@ +package esmeta.analyzer.util + +import esmeta.analyzer.Analyzer + +type Self = Decl & Analyzer + +trait Decl extends GraphDecl with DotPrinterDecl with StringifierDecl { + self: Self => +} diff --git a/src/main/scala/esmeta/error/AnalaysisError.scala b/src/main/scala/esmeta/error/AnalaysisError.scala index fab27f792a..208f7c5529 100644 --- a/src/main/scala/esmeta/error/AnalaysisError.scala +++ b/src/main/scala/esmeta/error/AnalaysisError.scala @@ -14,12 +14,6 @@ case class NotSupportedOperation(obj: Any, method: String) // imprecise case class AnalysisImprecise(msg: String) extends AnalysisError(msg) -// arity mismatches -case class AnalysisRemainingParams(ps: List[Param]) - extends AnalysisError(s"remaining parameters: ${ps.mkString(", ")}") -case class AnalysisRemainingArgs(as: List[AbsValue]) - extends AnalysisError(s"remaining arguments: ${as.mkString(", ")}") - // type mismatches case class TypeCheckFail(msg: Option[String]) extends AnalysisError("type check failed." + msg.fold("")(LINE_SEP + _)) diff --git a/src/main/scala/esmeta/interpreter/Interpreter.scala b/src/main/scala/esmeta/interpreter/Interpreter.scala index 614913cd2c..4098824f7f 100644 --- a/src/main/scala/esmeta/interpreter/Interpreter.scala +++ b/src/main/scala/esmeta/interpreter/Interpreter.scala @@ -26,7 +26,6 @@ class Interpreter( val log: Boolean = false, val logDir: String = EVAL_LOG_DIR, val timeLimit: Option[Int] = None, - val tycheck: Boolean = false, ) { import Interpreter.* @@ -72,16 +71,6 @@ class Interpreter( false case CallContext(retId, ctxt) :: rest => val (ret, value) = st.context.retVal.getOrElse(throw NoReturnValue) - if (tycheck) (st.typeOf(value), func.retTy.ty) match - case (actual: ValueTy, ty: ValueTy) if !ty.contains(value, st) => - val calleeRp = ReturnPoint(func, View()) - var msg = "" - val irp = InternalReturnPoint(ret, calleeRp) - msg += ReturnTypeMismatch(irp, actual) - msg += LINE_SEP + "- return : " + value - st.filename.map(msg += LINE_SEP + "- filename: " + _) - warn(msg) - case _ => st.context = ctxt st.callStack = rest setCallResult(retId, value) @@ -505,20 +494,6 @@ class Interpreter( case _: Cont => case _ => throw RemainingArgs(args) case (param :: pl, arg :: al) => - if (tycheck) param.ty.ty match - // ignore type check for `this` value - case _ if (func.isMethod || func.isSDO) && idx == 0 => - case ty: ValueTy if !ty.contains(arg, st) => - val callerNp = NodePoint(cfg.funcOf(caller), caller, View()) - val calleeNp = NodePoint(func, func.entry, View()) - val aap = ArgAssignPoint(CallPoint(callerNp, calleeNp), idx) - val argTy = st.typeOf(arg) - var msg = "" - msg += ParamTypeMismatch(aap, argTy) - msg += LINE_SEP + "- argument: " + arg - st.filename.map(msg += LINE_SEP + "- filename: " + _) - warn(msg) - case _ => map += param.lhs -> arg aux(pl, al, idx + 1) } @@ -637,8 +612,7 @@ object Interpreter { log: Boolean = false, logDir: String = EVAL_LOG_DIR, timeLimit: Option[Int] = None, - tycheck: Boolean = false, - ): State = new Interpreter(st, log, logDir, timeLimit, tycheck).result + ): State = new Interpreter(st, log, logDir, timeLimit).result // type update algorithms val setTypeMap: Map[String, String] = Map( diff --git a/src/main/scala/esmeta/phase/Analyze.scala b/src/main/scala/esmeta/phase/Analyze.scala index 69c3da466e..f31584a874 100644 --- a/src/main/scala/esmeta/phase/Analyze.scala +++ b/src/main/scala/esmeta/phase/Analyze.scala @@ -8,24 +8,26 @@ import esmeta.util.* import esmeta.util.SystemUtils.* /** `analyze` phase */ -case object Analyze extends Phase[CFG, AbsSemantics] { +case object Analyze extends Phase[CFG, ESAnalyzer#Semantics] { val name = "analyze" val help = "analyzes an ECMAScript file using meta-level static analysis." def apply( cfg: CFG, cmdConfig: CommandConfig, config: Config, - ): AbsSemantics = + ): ESAnalyzer#Semantics = val filename = getFirstFilename(cmdConfig, this.name) - val analyzer = ESAnalyzer(cfg) - analyzer(readFile(filename).trim) + val sourceText = readFile(filename).trim + ESAnalyzer(cfg, config.useRepl)(sourceText) def defaultConfig: Config = Config() val options: List[PhaseOption[Config]] = List( ( "repl", - BoolOption(c => USE_REPL = true), + BoolOption(c => c.useRepl = true), "use a REPL for meta-level static analysis.", ), ) - case class Config() + case class Config( + var useRepl: Boolean = false, + ) } diff --git a/src/main/scala/esmeta/phase/Eval.scala b/src/main/scala/esmeta/phase/Eval.scala index 1de66e544a..461c7d2928 100644 --- a/src/main/scala/esmeta/phase/Eval.scala +++ b/src/main/scala/esmeta/phase/Eval.scala @@ -32,7 +32,6 @@ case object Eval extends Phase[CFG, State] { Initialize.fromFile(cfg, filename), log = config.log, timeLimit = config.timeLimit, - tycheck = config.tycheck, ) def defaultConfig: Config = Config() @@ -42,11 +41,6 @@ case object Eval extends Phase[CFG, State] { NumOption((c, k) => c.timeLimit = Some(k)), "set the time limit in seconds (default: no limit).", ), - ( - "tycheck", - BoolOption(c => c.tycheck = true), - "turn on type check mode.", - ), ( "multiple", BoolOption(c => c.multiple = true), @@ -60,7 +54,6 @@ case object Eval extends Phase[CFG, State] { ) case class Config( var timeLimit: Option[Int] = None, - var tycheck: Boolean = false, var multiple: Boolean = false, var log: Boolean = false, ) diff --git a/src/main/scala/esmeta/phase/TypeCheck.scala b/src/main/scala/esmeta/phase/TypeCheck.scala index 10d2881d8f..db0b2f67f6 100644 --- a/src/main/scala/esmeta/phase/TypeCheck.scala +++ b/src/main/scala/esmeta/phase/TypeCheck.scala @@ -10,24 +10,28 @@ import esmeta.util.BaseUtils.* import esmeta.util.SystemUtils.* /** `tycheck` phase */ -case object TypeCheck extends Phase[CFG, AbsSemantics] { +case object TypeCheck extends Phase[CFG, TypeAnalyzer#Semantics] { val name = "tycheck" val help = "performs a type analysis of ECMA-262." def apply( cfg: CFG, cmdConfig: CommandConfig, config: Config, - ): AbsSemantics = + ): TypeAnalyzer#Semantics = val ignorePath = config.ignorePath match case None => cfg.spec.manualInfo.tycheckIgnore case path => path val ignore = ignorePath.fold(Ignore())(Ignore(_, config.ignoreUpdate)) - TypeAnalyzer(cfg)( - target = config.target, + TypeAnalyzer( + cfg = cfg, + targetPattern = config.target, + config = TypeAnalyzer.Config(), ignore = ignore, log = config.log, silent = false, - ) + useRepl = config.useRepl, + replContinue = config.replContinue, + ).result def defaultConfig: Config = Config() val options: List[PhaseOption[Config]] = List( @@ -38,12 +42,12 @@ case object TypeCheck extends Phase[CFG, AbsSemantics] { ), ( "repl", - BoolOption(c => USE_REPL = true), + BoolOption(c => c.useRepl = true), "use a REPL for type analysis of ECMA-262.", ), ( "repl-continue", - BoolOption(c => REPL_CONTINUE = true), + BoolOption(c => c.replContinue = true), "run `continue` command at startup when using REPL", ), ( @@ -66,6 +70,8 @@ case object TypeCheck extends Phase[CFG, AbsSemantics] { var target: Option[String] = None, var ignorePath: Option[String] = None, var ignoreUpdate: Boolean = false, + var useRepl: Boolean = false, + var replContinue: Boolean = false, var log: Boolean = false, ) } diff --git a/src/main/scala/esmeta/ty/AstValueTy.scala b/src/main/scala/esmeta/ty/AstValueTy.scala index 7a8419a2c1..b6a0a36d62 100644 --- a/src/main/scala/esmeta/ty/AstValueTy.scala +++ b/src/main/scala/esmeta/ty/AstValueTy.scala @@ -56,9 +56,6 @@ sealed trait AstValueTy extends TyElem with Lattice[AstValueTy] { case _ if this <= that => Bot case (AstNameTy(lset), AstNameTy(rset)) => AstNameTy(lset -- rset) case _ => this - - /** get single value */ - def getSingle: Flat[AValue] = Many } case object AstTopTy extends AstValueTy sealed trait AstNonTopTy extends AstValueTy { diff --git a/src/main/scala/esmeta/ty/BoolTy.scala b/src/main/scala/esmeta/ty/BoolTy.scala index 338470b6e2..2d8a92a769 100644 --- a/src/main/scala/esmeta/ty/BoolTy.scala +++ b/src/main/scala/esmeta/ty/BoolTy.scala @@ -29,9 +29,6 @@ case class BoolTy(set: Set[Boolean] = Set()) /** inclusion check */ def contains(b: Boolean): Boolean = set contains b - - /** get single value */ - def getSingle: Flat[Boolean] = Flat(set) } object BoolTy extends Parser.From(Parser.boolTy) { lazy val Top: BoolTy = BoolTy(Set(false, true)) diff --git a/src/main/scala/esmeta/ty/CompTy.scala b/src/main/scala/esmeta/ty/CompTy.scala index 251e14ebe2..d1917af9ea 100644 --- a/src/main/scala/esmeta/ty/CompTy.scala +++ b/src/main/scala/esmeta/ty/CompTy.scala @@ -64,11 +64,6 @@ case class CompTy( this.normal -- that.normal, this.abrupt -- that.abrupt, ) - - /** get single value */ - def getSingle: Flat[AValue] = - if (!abrupt.isBottom) Many - else normal.getSingle.map(AComp(Const("normal"), _, None)) } object CompTy extends Parser.From(Parser.compTy) { lazy val Bot: CompTy = CompTy() diff --git a/src/main/scala/esmeta/ty/ListTy.scala b/src/main/scala/esmeta/ty/ListTy.scala index 38eb337ed4..33f95f47ea 100644 --- a/src/main/scala/esmeta/ty/ListTy.scala +++ b/src/main/scala/esmeta/ty/ListTy.scala @@ -48,9 +48,6 @@ case class ListTy(elem: Option[ValueTy] = None) case (None, _) => Bot case (_, None) => this case (Some(l), Some(r)) => ListTy(Some(l -- r)) - - /** get single value */ - def getSingle: Flat[Nothing] = if (elem.isEmpty) Zero else Many } object ListTy extends Parser.From(Parser.listTy) { lazy val Top: ListTy = ListTy(Some(ValueTy.Top)) diff --git a/src/main/scala/esmeta/ty/NameTy.scala b/src/main/scala/esmeta/ty/NameTy.scala index 51f9f46f1e..5ec99f0be1 100644 --- a/src/main/scala/esmeta/ty/NameTy.scala +++ b/src/main/scala/esmeta/ty/NameTy.scala @@ -52,9 +52,6 @@ case class NameTy(set: BSet[String] = Fin()) def norm: NameTy = this.set match case Inf => this case Fin(set) => NameTy(Fin(set.filter(x => !isSubTy(x, set - x)))) - - /** get single value */ - def getSingle: Flat[Nothing] = if (isBottom) Zero else Many } object NameTy extends Parser.From(Parser.nameTy) { lazy val Top: NameTy = NameTy(Inf) diff --git a/src/main/scala/esmeta/ty/PureValueTy.scala b/src/main/scala/esmeta/ty/PureValueTy.scala index cb3345c3d9..ec8e7aada7 100644 --- a/src/main/scala/esmeta/ty/PureValueTy.scala +++ b/src/main/scala/esmeta/ty/PureValueTy.scala @@ -168,27 +168,6 @@ sealed trait PureValueTy extends TyElem with Lattice[PureValueTy] { this.absent -- that.absent, ) - /** get single value */ - def getSingle: Flat[APureValue] = - (if (this.clo.isBottom) Zero else Many) || - (if (this.cont.isBottom) Zero else Many) || - (if (this.name.isBottom) Zero else Many) || - (if (this.record.isBottom) Zero else Many) || - (if (this.list.isBottom) Zero else Many) || - (if (this.symbol.isBottom) Zero else Many) || - (if (this.astValue.isBottom) Zero else Many) || - nt.getSingle || - (if (this.codeUnit.isBottom) Zero else Many) || - (const.getSingle.map(Const(_): APureValue)) || - (math.getSingle.map(Math(_): APureValue)) || - number.getSingle || - (if (this.bigInt.isBottom) Zero else Many) || - (str.getSingle.map(Str(_): APureValue)) || - (bool.getSingle.map(Bool(_): APureValue)) || - (if (this.undef.isBottom) Zero else One(Undef)) || - (if (this.nullv.isBottom) Zero else One(Null)) || - (if (this.absent.isBottom) Zero else One(Absent)) - /** normalization */ def norm: PureValueTy = if ( clo.isTop && diff --git a/src/main/scala/esmeta/ty/RecordTy.scala b/src/main/scala/esmeta/ty/RecordTy.scala index 4e277e1219..bdf8644fd8 100644 --- a/src/main/scala/esmeta/ty/RecordTy.scala +++ b/src/main/scala/esmeta/ty/RecordTy.scala @@ -65,9 +65,6 @@ sealed trait RecordTy extends TyElem with Lattice[RecordTy] { case Elem(map) => val newMap = map.filter { case (_, v) => !v.isBottom } if (newMap.isEmpty) Bot else Elem(newMap) - - /** get single value */ - def getSingle: Flat[Nothing] = if (isBottom) Zero else Many } case object RecordTopTy extends RecordTy case class RecordElemTy(map: Map[String, ValueTy] = Map()) extends RecordTy diff --git a/src/main/scala/esmeta/ty/SubMapTy.scala b/src/main/scala/esmeta/ty/SubMapTy.scala index b36565d0dd..a7b38f0afd 100644 --- a/src/main/scala/esmeta/ty/SubMapTy.scala +++ b/src/main/scala/esmeta/ty/SubMapTy.scala @@ -57,9 +57,6 @@ case class SubMapTy( this.value -- that.value, ).norm - /** get single value */ - def getSingle: Flat[AValue] = if (this.isBottom) Zero else Many - // normalization private def norm: SubMapTy = if (key.isBottom || value.isBottom) Bot diff --git a/src/main/scala/esmeta/ty/ValueTy.scala b/src/main/scala/esmeta/ty/ValueTy.scala index fc7d3a50dd..a12868b73c 100644 --- a/src/main/scala/esmeta/ty/ValueTy.scala +++ b/src/main/scala/esmeta/ty/ValueTy.scala @@ -75,12 +75,6 @@ case class ValueTy( this.subMap -- that.subMap, ) - /** get single value */ - def getSingle: Flat[AValue] = - this.comp.getSingle || - this.pureValue.getSingle || - this.subMap.getSingle - /** completion check */ def isCompletion: Boolean = !comp.isBottom && diff --git a/src/main/scala/esmeta/util/IndentParsers.scala b/src/main/scala/esmeta/util/IndentParsers.scala index 006d8558ab..841eb2f7c0 100644 --- a/src/main/scala/esmeta/util/IndentParsers.scala +++ b/src/main/scala/esmeta/util/IndentParsers.scala @@ -101,9 +101,9 @@ trait IndentParsers extends BasicParsers with EPackratParsers { })(in) } - // ------------------------------------------------------------------------------ + // --------------------------------------------------------------------------- // locational parser - // ------------------------------------------------------------------------------ + // --------------------------------------------------------------------------- override def locationed[T <: Locational]( p: => Parser[T], ): LocationalParser[T] = diff --git a/src/test/scala/esmeta/analyzer/AnalyzerTest.scala b/src/test/scala/esmeta/analyzer/AnalyzerTest.scala index 6f34c67f6e..dbb758fc5a 100644 --- a/src/test/scala/esmeta/analyzer/AnalyzerTest.scala +++ b/src/test/scala/esmeta/analyzer/AnalyzerTest.scala @@ -6,3 +6,8 @@ import esmeta.ESMetaTest trait AnalyzerTest extends ESMetaTest { def category: String = "analyzer" } +object AnalyzerTest { + import ESMetaTest.* + + lazy val analyzer: Analyzer = TypeAnalyzer(cfg) +} diff --git a/src/test/scala/esmeta/analyzer/StringifyTinyTest.scala b/src/test/scala/esmeta/analyzer/StringifyTinyTest.scala index 44c990ebb5..73d2115115 100644 --- a/src/test/scala/esmeta/analyzer/StringifyTinyTest.scala +++ b/src/test/scala/esmeta/analyzer/StringifyTinyTest.scala @@ -6,6 +6,8 @@ import esmeta.ty.* import scala.collection.mutable.ListBuffer class StringifyTinyTest extends AnalyzerTest { + import AnalyzerTest.*, analyzer.* + val name: String = "analyzerStringifyTest" // registration @@ -20,7 +22,9 @@ class StringifyTinyTest extends AnalyzerTest { loops = List(loopCtxt, loopCtxt), ) checkStringify("View")( - insensView -> "[call: ][loop: ]", + View() -> "", + View(calls = List(call, call)) -> "[call: 0, 0]", + View(loops = List(loopCtxt)) -> "[loop: 0(3)]", irView -> "[call: 0, 0, 0][loop: 0(3), 0(3)]", ) diff --git a/src/test/scala/esmeta/es/AnalyzeSmallTest.scala b/src/test/scala/esmeta/es/AnalyzeSmallTest.scala index 9ad4ebd281..9136e8e22c 100644 --- a/src/test/scala/esmeta/es/AnalyzeSmallTest.scala +++ b/src/test/scala/esmeta/es/AnalyzeSmallTest.scala @@ -8,7 +8,6 @@ import esmeta.util.SystemUtils.* class AnalyzeSmallTest extends ESTest { import ESTest.* - YET_THROW = true val name: String = "esAnalyzeTest" // registration diff --git a/src/test/scala/esmeta/es/ESTest.scala b/src/test/scala/esmeta/es/ESTest.scala index 9fc8d2d441..246b9b1dd2 100644 --- a/src/test/scala/esmeta/es/ESTest.scala +++ b/src/test/scala/esmeta/es/ESTest.scala @@ -65,11 +65,12 @@ object ESTest { // --------------------------------------------------------------------------- // analyzer lazy val analyzer = ESAnalyzer(cfg) + import analyzer.* // analyze ES codes - def analyzeFile(filename: String): AbsSemantics = + def analyzeFile(filename: String): analyzer.Semantics = analyzer(readFile(filename).trim) - def analyze(str: String): AbsSemantics = analyzer(str) + def analyze(str: String): Semantics = analyzer(str) // tests for ES parser def parseTest(ast: Ast): Ast = From d8931137af9d71ff7636d70236167bcb30184713 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Thu, 8 Feb 2024 05:07:04 +0900 Subject: [PATCH 02/11] Add analyzerTypeCheckTest --- build.sbt | 5 + .../d048f32e861c2ed4/tycheck-ignore.json | 2 +- .../scala/esmeta/analyzer/TypeAnalyzer.scala | 92 ++++++++++--------- .../domain/value/ValueTypeDomain.scala | 3 + src/main/scala/esmeta/phase/TypeCheck.scala | 16 ++-- src/main/scala/esmeta/ty/AstValueTy.scala | 1 - src/main/scala/esmeta/ty/BoolTy.scala | 3 + src/main/scala/esmeta/ty/CompTy.scala | 6 +- src/main/scala/esmeta/ty/PureValueTy.scala | 22 ++++- src/main/scala/esmeta/ty/SubMapTy.scala | 5 +- src/main/scala/esmeta/ty/ValueTy.scala | 7 +- .../scala/esmeta/analyzer/AnalyzerTest.scala | 9 +- .../esmeta/analyzer/TypeCheckSmallTest.scala | 17 ++++ tests/result/analyzer/TypeCheckSmallTest | 1 + tests/result/analyzer/TypeCheckSmallTest.json | 3 + 15 files changed, 135 insertions(+), 57 deletions(-) create mode 100644 src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala create mode 100644 tests/result/analyzer/TypeCheckSmallTest create mode 100644 tests/result/analyzer/TypeCheckSmallTest.json diff --git a/build.sbt b/build.sbt index 137750ea0d..b41f77c574 100644 --- a/build.sbt +++ b/build.sbt @@ -110,6 +110,8 @@ lazy val stateStringifyTest = lazy val analyzerTest = taskKey[Unit]("Launch analyzer tests") lazy val analyzerStringifyTest = taskKey[Unit]("Launch stringify tests for analyzer (tiny)") +lazy val analyzerTypeCheckTest = + taskKey[Unit]("Launch typecheck tests for analyzer (small)") // es lazy val esTest = taskKey[Unit]("Launch ECMAScript tests") @@ -259,6 +261,9 @@ lazy val root = project analyzerStringifyTest := (Test / testOnly) .toTask(" *.analyzer.Stringify*Test") .value, + analyzerTypeCheckTest := (Test / testOnly) + .toTask(" *.analyzer.TypeCheck*Test") + .value, // es esTest := (Test / testOnly).toTask(" *.es.*Test").value, esEvalTest := (Test / testOnly).toTask(" *.es.Eval*Test").value, diff --git a/src/main/resources/manuals/d048f32e861c2ed4/tycheck-ignore.json b/src/main/resources/manuals/d048f32e861c2ed4/tycheck-ignore.json index 1fd4137fed..fe5f1a8961 100644 --- a/src/main/resources/manuals/d048f32e861c2ed4/tycheck-ignore.json +++ b/src/main/resources/manuals/d048f32e861c2ed4/tycheck-ignore.json @@ -49,4 +49,4 @@ "Statement[1,0].LabelledEvaluation", "Statement[2,0].LabelledEvaluation", "StringCreate" -] +] \ No newline at end of file diff --git a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala index 286fd278f6..8a46a7f11a 100644 --- a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala +++ b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala @@ -3,7 +3,6 @@ package esmeta.analyzer import esmeta.{ANALYZE_LOG_DIR, LINE_SEP} import esmeta.analyzer.domain.* import esmeta.cfg.* -import esmeta.error.* import esmeta.es.* import esmeta.ir.{Func => IRFunc, *} import esmeta.ty.* @@ -27,13 +26,27 @@ class TypeAnalyzer( ) extends Analyzer { import TypeAnalyzer.* - /** perform type analysis with the given control flow graph */ - lazy val result: Semantics = + lazy val analyze: Semantics = AbsState.setBase(new Initialize(cfg)) transfer.fixpoint - postProcess + if (log) logging + if (ignore.update) updateIgnore sem + /** unused ignore set */ + private var _unusedSet: Set[String] = ignore.names + inline def unusedSet: Set[String] = _unusedSet + + /** perform type analysis with the given control flow graph */ + lazy val mismatches: Set[TypeMismatch] = mismatchMap.values.toSet + lazy val detected = + analyze + mismatches.filter(mismatch => { + val name = mismatch.func.name + _unusedSet -= name + !ignore.names.contains(name) + }) + /** all possible initial analysis target functions */ def targetFuncs: List[Func] = val allFuncs = cfg.funcs.filter(_.isParamTysDefined) @@ -47,9 +60,8 @@ class TypeAnalyzer( funcs /** record type mismatches */ - def addMismatch(mismatch: TypeMismatch): Unit = + private def addMismatch(mismatch: TypeMismatch): Unit = mismatchMap += mismatch.ap -> mismatch - lazy val mismatches: Set[TypeMismatch] = mismatchMap.values.toSet private var mismatchMap: Map[AnalysisPoint, TypeMismatch] = Map() /** no sensitivity */ @@ -197,44 +209,27 @@ class TypeAnalyzer( retDomain = Some(RetTypeDomain) valueDomain = Some(ValueTypeDomain) - /** post-: t-Domainprocessing */ - def postProcess: Unit = - if (log) logging - var unusedSet = ignore.names - val detected = mismatches.filter(mismatch => { - val name = mismatch.func.name - unusedSet -= name - !ignore.names.contains(name) - }) - if (!detected.isEmpty || !unusedSet.isEmpty) - val app = new Appender - // show detected type mismatches - if (!detected.isEmpty) - app :> "* " >> detected.size - app >> " type mismatches are detected." - // show unused names - if (!unusedSet.isEmpty) - app :> "* " >> unusedSet.size - app >> " names are not used to ignore mismatches." - detected.toList.map(_.toString).sorted.map(app :> _) - // show help message about how to use the ignorance system - ignore.filename.map(path => - if (ignore.update) - dumpJson( - name = "algorithm names for the ignorance system", - data = mismatches.map(_.func.name).toList.sorted, - filename = path, - noSpace = false, - ) - else - app :> "=" * 80 - app :> "To suppress this error message, " - app >> "add/remove the following names to `" >> path >> "`:" - detected.map(_.func.name).toList.sorted.map(app :> " + " >> _) - unusedSet.toList.sorted.map(app :> " - " >> _) - app :> "=" * 80, - ) - throw TypeCheckFail(if (silent) None else Some(app.toString)) + /** conversion to string */ + override def toString: String = + val app = new Appender + // show detected type mismatches + if (!detected.isEmpty) + app :> "* " >> detected.size + app >> " type mismatches are detected." + // show unused names + if (!unusedSet.isEmpty) + app :> "* " >> unusedSet.size + app >> " names are not used to ignore mismatches." + detected.toList.map(_.toString).sorted.map(app :> _) + // show help message about how to use the ignorance system + for (path <- ignore.filename if !ignore.update) + app :> "=" * 80 + app :> "To suppress this error message, " + app >> "add/remove the following names to `" >> path >> "`:" + detected.map(_.func.name).toList.sorted.map(app :> " + " >> _) + unusedSet.toList.sorted.map(app :> " - " >> _) + app :> "=" * 80 + app.toString // --------------------------------------------------------------------------- // private helpers @@ -293,6 +288,15 @@ class TypeAnalyzer( filename = s"$ANALYZE_LOG_DIR/mismatches", ) } + + /** update ignorance system */ + private def updateIgnore: Unit = for (path <- ignore.filename) + dumpJson( + name = "algorithm names for the ignorance system", + data = mismatches.map(_.func.name).toList.sorted, + filename = path, + noSpace = false, + ) } object TypeAnalyzer: diff --git a/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala b/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala index 3517b95766..073e742fa4 100644 --- a/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala +++ b/src/main/scala/esmeta/analyzer/domain/value/ValueTypeDomain.scala @@ -131,6 +131,9 @@ trait ValueTypeDomainDecl { self: Self => /** concretization function */ override def gamma: BSet[AValue] = Inf + /** get single string value */ + override def getSingle: Flat[AValue] = elem.ty.getSingle.map(AValue.from) + /** get reachable address partitions */ def reachableParts: Set[Part] = Set() diff --git a/src/main/scala/esmeta/phase/TypeCheck.scala b/src/main/scala/esmeta/phase/TypeCheck.scala index db0b2f67f6..ed845b1c77 100644 --- a/src/main/scala/esmeta/phase/TypeCheck.scala +++ b/src/main/scala/esmeta/phase/TypeCheck.scala @@ -5,6 +5,7 @@ import esmeta.analyzer.* import esmeta.analyzer.TypeAnalyzer.Ignore import esmeta.analyzer.domain import esmeta.cfg.{CFG, Func} +import esmeta.error.TypeCheckFail import esmeta.util.* import esmeta.util.BaseUtils.* import esmeta.util.SystemUtils.* @@ -18,20 +19,23 @@ case object TypeCheck extends Phase[CFG, TypeAnalyzer#Semantics] { cmdConfig: CommandConfig, config: Config, ): TypeAnalyzer#Semantics = - val ignorePath = config.ignorePath match - case None => cfg.spec.manualInfo.tycheckIgnore - case path => path + val ignorePath = config.ignorePath.orElse(cfg.spec.manualInfo.tycheckIgnore) val ignore = ignorePath.fold(Ignore())(Ignore(_, config.ignoreUpdate)) - TypeAnalyzer( + val silent = cmdConfig.silent + val analyzer: TypeAnalyzer = TypeAnalyzer( cfg = cfg, targetPattern = config.target, config = TypeAnalyzer.Config(), ignore = ignore, log = config.log, - silent = false, + silent = silent, useRepl = config.useRepl, replContinue = config.replContinue, - ).result + ) + val sem = analyzer.analyze + if (analyzer.detected.nonEmpty || analyzer.unusedSet.nonEmpty) + throw TypeCheckFail(if (silent) None else Some(analyzer.toString)) + sem def defaultConfig: Config = Config() val options: List[PhaseOption[Config]] = List( diff --git a/src/main/scala/esmeta/ty/AstValueTy.scala b/src/main/scala/esmeta/ty/AstValueTy.scala index b6a0a36d62..373d04d01e 100644 --- a/src/main/scala/esmeta/ty/AstValueTy.scala +++ b/src/main/scala/esmeta/ty/AstValueTy.scala @@ -1,6 +1,5 @@ package esmeta.ty -import esmeta.analyzer.domain.* import esmeta.util.* import esmeta.state.* import esmeta.ty.util.Parser diff --git a/src/main/scala/esmeta/ty/BoolTy.scala b/src/main/scala/esmeta/ty/BoolTy.scala index 2d8a92a769..338470b6e2 100644 --- a/src/main/scala/esmeta/ty/BoolTy.scala +++ b/src/main/scala/esmeta/ty/BoolTy.scala @@ -29,6 +29,9 @@ case class BoolTy(set: Set[Boolean] = Set()) /** inclusion check */ def contains(b: Boolean): Boolean = set contains b + + /** get single value */ + def getSingle: Flat[Boolean] = Flat(set) } object BoolTy extends Parser.From(Parser.boolTy) { lazy val Top: BoolTy = BoolTy(Set(false, true)) diff --git a/src/main/scala/esmeta/ty/CompTy.scala b/src/main/scala/esmeta/ty/CompTy.scala index d1917af9ea..a4dc6f58b8 100644 --- a/src/main/scala/esmeta/ty/CompTy.scala +++ b/src/main/scala/esmeta/ty/CompTy.scala @@ -1,6 +1,5 @@ package esmeta.ty -import esmeta.analyzer.domain.* import esmeta.util.* import esmeta.state.* import esmeta.ty.util.Parser @@ -64,6 +63,11 @@ case class CompTy( this.normal -- that.normal, this.abrupt -- that.abrupt, ) + + /** get single value */ + def getSingle: Flat[Value] = + if (!abrupt.isBottom) Many + else normal.getSingle.map(Comp(Const("normal"), _, None)) } object CompTy extends Parser.From(Parser.compTy) { lazy val Bot: CompTy = CompTy() diff --git a/src/main/scala/esmeta/ty/PureValueTy.scala b/src/main/scala/esmeta/ty/PureValueTy.scala index ec8e7aada7..a34ea1a32f 100644 --- a/src/main/scala/esmeta/ty/PureValueTy.scala +++ b/src/main/scala/esmeta/ty/PureValueTy.scala @@ -4,7 +4,6 @@ import esmeta.cfg.Func import esmeta.state.* import esmeta.ty.util.Parser import esmeta.util.* -import esmeta.analyzer.domain.* /** pure value types (non-completion record types) */ sealed trait PureValueTy extends TyElem with Lattice[PureValueTy] { @@ -199,6 +198,27 @@ sealed trait PureValueTy extends TyElem with Lattice[PureValueTy] { list = elem.list.copy(elem.list.elem.map(_.removeAbsent)), absent = false, ) + + /** get single value */ + def getSingle: Flat[PureValue] = + (if (this.clo.isBottom) Zero else Many) || + (if (this.cont.isBottom) Zero else Many) || + (if (this.name.isBottom) Zero else Many) || + (if (this.record.isBottom) Zero else Many) || + (if (this.list.isBottom) Zero else Many) || + (if (this.symbol.isBottom) Zero else Many) || + (if (this.astValue.isBottom) Zero else Many) || + nt.getSingle || + (if (this.codeUnit.isBottom) Zero else Many) || + (const.getSingle.map(Const(_): PureValue)) || + (math.getSingle.map(Math(_): PureValue)) || + number.getSingle || + (if (this.bigInt.isBottom) Zero else Many) || + (str.getSingle.map(Str(_): PureValue)) || + (bool.getSingle.map(Bool(_): PureValue)) || + (if (this.undef.isBottom) Zero else One(Undef)) || + (if (this.nullv.isBottom) Zero else One(Null)) || + (if (this.absent.isBottom) Zero else One(Absent)) } case object PureValueTopTy extends PureValueTy { diff --git a/src/main/scala/esmeta/ty/SubMapTy.scala b/src/main/scala/esmeta/ty/SubMapTy.scala index a7b38f0afd..55b3fd8a40 100644 --- a/src/main/scala/esmeta/ty/SubMapTy.scala +++ b/src/main/scala/esmeta/ty/SubMapTy.scala @@ -1,7 +1,7 @@ package esmeta.ty -import esmeta.analyzer.domain.* import esmeta.util.* +import esmeta.state.Value import esmeta.ty.util.Parser /** sub map types */ @@ -57,6 +57,9 @@ case class SubMapTy( this.value -- that.value, ).norm + /** get single value */ + def getSingle: Flat[Value] = if (this.isBottom) Zero else Many + // normalization private def norm: SubMapTy = if (key.isBottom || value.isBottom) Bot diff --git a/src/main/scala/esmeta/ty/ValueTy.scala b/src/main/scala/esmeta/ty/ValueTy.scala index a12868b73c..390de7c1a4 100644 --- a/src/main/scala/esmeta/ty/ValueTy.scala +++ b/src/main/scala/esmeta/ty/ValueTy.scala @@ -1,6 +1,5 @@ package esmeta.ty -import esmeta.analyzer.domain.* import esmeta.cfg.Func import esmeta.state.* import esmeta.util.* @@ -158,6 +157,12 @@ case class ValueTy( case Null => nullv case Absent => absent ) + + /** get single value */ + def getSingle: Flat[Value] = + this.comp.getSingle || + this.pureValue.getSingle || + this.subMap.getSingle } object ValueTy extends Parser.From(Parser.valueTy) { def apply( diff --git a/src/test/scala/esmeta/analyzer/AnalyzerTest.scala b/src/test/scala/esmeta/analyzer/AnalyzerTest.scala index dbb758fc5a..0fc43901ef 100644 --- a/src/test/scala/esmeta/analyzer/AnalyzerTest.scala +++ b/src/test/scala/esmeta/analyzer/AnalyzerTest.scala @@ -1,6 +1,7 @@ package esmeta.analyzer import esmeta.ESMetaTest +import esmeta.analyzer.TypeAnalyzer.Ignore /** analyzer tests */ trait AnalyzerTest extends ESMetaTest { @@ -9,5 +10,11 @@ trait AnalyzerTest extends ESMetaTest { object AnalyzerTest { import ESMetaTest.* - lazy val analyzer: Analyzer = TypeAnalyzer(cfg) + lazy val ignorePath = cfg.spec.manualInfo.tycheckIgnore + lazy val ignore = ignorePath.fold(Ignore())(Ignore(_, update = true)) + lazy val analyzer: TypeAnalyzer = TypeAnalyzer( + cfg = cfg, + ignore = ignore, + silent = true, + ) } diff --git a/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala b/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala new file mode 100644 index 0000000000..01429cdebc --- /dev/null +++ b/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala @@ -0,0 +1,17 @@ +package esmeta.analyzer + +class TypeCheckSmallTest extends AnalyzerTest { + import AnalyzerTest.*, analyzer.* + + val name: String = "analyzerTypeCheckTest" + + // registration + def init: Unit = { + check("recent") { + assert(analyzer.detected.isEmpty) + assert(analyzer.unusedSet.isEmpty) + } + } + + init +} diff --git a/tests/result/analyzer/TypeCheckSmallTest b/tests/result/analyzer/TypeCheckSmallTest new file mode 100644 index 0000000000..55ce4b4566 --- /dev/null +++ b/tests/result/analyzer/TypeCheckSmallTest @@ -0,0 +1 @@ +analyzer.TypeCheckSmallTest: 1 / 1 diff --git a/tests/result/analyzer/TypeCheckSmallTest.json b/tests/result/analyzer/TypeCheckSmallTest.json new file mode 100644 index 0000000000..1b0b5b856a --- /dev/null +++ b/tests/result/analyzer/TypeCheckSmallTest.json @@ -0,0 +1,3 @@ +{ + "recent" : true +} From 998f7c2ae5cecaa0ddf9269921e387b3253e4fcc Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Fri, 9 Feb 2024 00:19:06 +0900 Subject: [PATCH 03/11] Update ManualInfo / Refactor TypeAnalyzer --- src/main/resources/manuals/default/hash | 1 + .../tycheck-ignore.json | 0 .../{d711ba960cd12b76 => es2022}/bugfix.patch | 0 .../funcs/HostEnsureCanCompileStrings.ir | 0 .../{d711ba960cd12b76 => es2022}/rule.json | 0 .../manuals/{d711ba960cd12b76 => es2022}/tag | 0 .../tycheck-ignore.json | 7 ++- .../{d048f32e861c2ed4 => es2023}/bugfix.patch | 0 .../manuals/{d048f32e861c2ed4 => es2023}/tag | 0 .../manuals/es2023/tycheck-ignore.json | 52 ++++++++++++++++ .../scala/esmeta/analyzer/TypeAnalyzer.scala | 60 +++++++++---------- src/main/scala/esmeta/phase/TypeCheck.scala | 12 ++-- src/main/scala/esmeta/util/ManualInfo.scala | 27 +++++---- .../scala/esmeta/analyzer/AnalyzerTest.scala | 2 +- .../esmeta/analyzer/TypeCheckSmallTest.scala | 1 + 15 files changed, 111 insertions(+), 51 deletions(-) create mode 100644 src/main/resources/manuals/default/hash rename src/main/resources/manuals/{d048f32e861c2ed4 => default}/tycheck-ignore.json (100%) rename src/main/resources/manuals/{d711ba960cd12b76 => es2022}/bugfix.patch (100%) rename src/main/resources/manuals/{d711ba960cd12b76 => es2022}/funcs/HostEnsureCanCompileStrings.ir (100%) rename src/main/resources/manuals/{d711ba960cd12b76 => es2022}/rule.json (100%) rename src/main/resources/manuals/{d711ba960cd12b76 => es2022}/tag (100%) rename src/main/resources/manuals/{d711ba960cd12b76 => es2022}/tycheck-ignore.json (94%) rename src/main/resources/manuals/{d048f32e861c2ed4 => es2023}/bugfix.patch (100%) rename src/main/resources/manuals/{d048f32e861c2ed4 => es2023}/tag (100%) create mode 100644 src/main/resources/manuals/es2023/tycheck-ignore.json diff --git a/src/main/resources/manuals/default/hash b/src/main/resources/manuals/default/hash new file mode 100644 index 0000000000..6b9830694c --- /dev/null +++ b/src/main/resources/manuals/default/hash @@ -0,0 +1 @@ +3a773fc9fae58be023228b13dbbd402ac18eeb6b diff --git a/src/main/resources/manuals/d048f32e861c2ed4/tycheck-ignore.json b/src/main/resources/manuals/default/tycheck-ignore.json similarity index 100% rename from src/main/resources/manuals/d048f32e861c2ed4/tycheck-ignore.json rename to src/main/resources/manuals/default/tycheck-ignore.json diff --git a/src/main/resources/manuals/d711ba960cd12b76/bugfix.patch b/src/main/resources/manuals/es2022/bugfix.patch similarity index 100% rename from src/main/resources/manuals/d711ba960cd12b76/bugfix.patch rename to src/main/resources/manuals/es2022/bugfix.patch diff --git a/src/main/resources/manuals/d711ba960cd12b76/funcs/HostEnsureCanCompileStrings.ir b/src/main/resources/manuals/es2022/funcs/HostEnsureCanCompileStrings.ir similarity index 100% rename from src/main/resources/manuals/d711ba960cd12b76/funcs/HostEnsureCanCompileStrings.ir rename to src/main/resources/manuals/es2022/funcs/HostEnsureCanCompileStrings.ir diff --git a/src/main/resources/manuals/d711ba960cd12b76/rule.json b/src/main/resources/manuals/es2022/rule.json similarity index 100% rename from src/main/resources/manuals/d711ba960cd12b76/rule.json rename to src/main/resources/manuals/es2022/rule.json diff --git a/src/main/resources/manuals/d711ba960cd12b76/tag b/src/main/resources/manuals/es2022/tag similarity index 100% rename from src/main/resources/manuals/d711ba960cd12b76/tag rename to src/main/resources/manuals/es2022/tag diff --git a/src/main/resources/manuals/d711ba960cd12b76/tycheck-ignore.json b/src/main/resources/manuals/es2022/tycheck-ignore.json similarity index 94% rename from src/main/resources/manuals/d711ba960cd12b76/tycheck-ignore.json rename to src/main/resources/manuals/es2022/tycheck-ignore.json index 100e426c9a..066cdee3ed 100644 --- a/src/main/resources/manuals/d711ba960cd12b76/tycheck-ignore.json +++ b/src/main/resources/manuals/es2022/tycheck-ignore.json @@ -16,9 +16,12 @@ "BoundFunctionCreate", "Catch[0,0].CatchClauseEvaluation", "Catch[1,0].CatchClauseEvaluation", + "ClassElement[0,0].ClassElementEvaluation", + "ClassElement[1,0].ClassElementEvaluation", "ClassStaticBlockBody[0,0].EvaluateClassStaticBlockBody", "CreateArrayIterator", "CreateBuiltinFunction", + "CreateMappedArgumentsObject", "CreateUnmappedArgumentsObject", "ECMAScriptFunctionObject.Construct", "FlattenIntoArray", @@ -36,7 +39,6 @@ "LabelledItem[1,0].LabelledEvaluation", "LengthOfArrayLike", "ModuleNamespaceCreate", - "ModuleNamespaceExoticObject.OwnPropertyKeys", "OptionalChain[1,0].ChainEvaluation", "OptionalChain[2,0].ChainEvaluation", "OptionalChain[4,0].ChainEvaluation", @@ -44,7 +46,6 @@ "OptionalChain[7,0].ChainEvaluation", "OptionalChain[9,0].ChainEvaluation", "OrdinaryFunctionCreate", - "OrdinaryObject.OwnPropertyKeys", "ProxyCreate", "SerializeJSONObject", "SetFunctionLength", @@ -57,4 +58,4 @@ "StringCreate", "ToBigInt", "ToNumber" -] +] \ No newline at end of file diff --git a/src/main/resources/manuals/d048f32e861c2ed4/bugfix.patch b/src/main/resources/manuals/es2023/bugfix.patch similarity index 100% rename from src/main/resources/manuals/d048f32e861c2ed4/bugfix.patch rename to src/main/resources/manuals/es2023/bugfix.patch diff --git a/src/main/resources/manuals/d048f32e861c2ed4/tag b/src/main/resources/manuals/es2023/tag similarity index 100% rename from src/main/resources/manuals/d048f32e861c2ed4/tag rename to src/main/resources/manuals/es2023/tag diff --git a/src/main/resources/manuals/es2023/tycheck-ignore.json b/src/main/resources/manuals/es2023/tycheck-ignore.json new file mode 100644 index 0000000000..fe5f1a8961 --- /dev/null +++ b/src/main/resources/manuals/es2023/tycheck-ignore.json @@ -0,0 +1,52 @@ +[ + "AddEntriesFromIterable", + "ArrayAssignmentPattern[0,0].DestructuringAssignmentEvaluation", + "ArrayAssignmentPattern[0,1].DestructuringAssignmentEvaluation", + "ArrayAssignmentPattern[0,2].DestructuringAssignmentEvaluation", + "ArrayAssignmentPattern[0,3].DestructuringAssignmentEvaluation", + "ArrayAssignmentPattern[1,0].DestructuringAssignmentEvaluation", + "ArrayAssignmentPattern[2,0].DestructuringAssignmentEvaluation", + "ArrayAssignmentPattern[2,1].DestructuringAssignmentEvaluation", + "ArrayAssignmentPattern[2,2].DestructuringAssignmentEvaluation", + "ArrayAssignmentPattern[2,3].DestructuringAssignmentEvaluation", + "ArrayCreate", + "AsyncGeneratorBody[0,0].EvaluateAsyncGeneratorBody", + "AsyncGeneratorUnwrapYieldResumption", + "BindingPattern[1,0].BindingInitialization", + "BoundFunctionCreate", + "Catch[0,0].CatchClauseEvaluation", + "Catch[1,0].CatchClauseEvaluation", + "ClassStaticBlockBody[0,0].EvaluateClassStaticBlockBody", + "CreateArrayIterator", + "CreateBuiltinFunction", + "CreateIterResultObject", + "ECMAScriptFunctionObject.Call", + "ECMAScriptFunctionObject.Construct", + "FindViaPredicate", + "FlattenIntoArray", + "ForIn/OfBodyEvaluation", + "FunctionBody[0,0].EvaluateFunctionBody", + "GeneratorBody[0,0].EvaluateGeneratorBody", + "GetGlobalObject", + "GetMethod", + "GetPromiseResolve", + "GetThisValue", + "INTRINSICS.DefaultTimeZone", + "InnerModuleEvaluation", + "IntegerIndexedObjectCreate", + "LabelledItem[1,0].LabelledEvaluation", + "LengthOfArrayLike", + "MethodDefinition[0,0].DefineMethod", + "ModuleNamespaceCreate", + "ProxyCreate", + "SerializeJSONObject", + "SetFunctionLength", + "SortIndexedProperties", + "SourceTextModuleRecord.ExecuteModule", + "SourceTextModuleRecord.ResolveExport", + "SpeciesConstructor", + "Statement[0,0].LabelledEvaluation", + "Statement[1,0].LabelledEvaluation", + "Statement[2,0].LabelledEvaluation", + "StringCreate" +] \ No newline at end of file diff --git a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala index 8a46a7f11a..cf425f9c98 100644 --- a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala +++ b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala @@ -26,26 +26,17 @@ class TypeAnalyzer( ) extends Analyzer { import TypeAnalyzer.* - lazy val analyze: Semantics = - AbsState.setBase(new Initialize(cfg)) - transfer.fixpoint - if (log) logging - if (ignore.update) updateIgnore - sem - /** unused ignore set */ - private var _unusedSet: Set[String] = ignore.names + protected var _unusedSet: Set[String] = ignore.names inline def unusedSet: Set[String] = _unusedSet /** perform type analysis with the given control flow graph */ lazy val mismatches: Set[TypeMismatch] = mismatchMap.values.toSet - lazy val detected = - analyze - mismatches.filter(mismatch => { - val name = mismatch.func.name - _unusedSet -= name - !ignore.names.contains(name) - }) + lazy val detected = mismatches.filter(mismatch => { + val name = mismatch.func.name + _unusedSet -= name + !ignore.names.contains(name) + }) /** all possible initial analysis target functions */ def targetFuncs: List[Func] = @@ -59,10 +50,17 @@ class TypeAnalyzer( if (!silent) println(s"- ${funcs.size} functions are initial targets.") funcs - /** record type mismatches */ - private def addMismatch(mismatch: TypeMismatch): Unit = - mismatchMap += mismatch.ap -> mismatch - private var mismatchMap: Map[AnalysisPoint, TypeMismatch] = Map() + /** check if the ignore set needs to be updated */ + def needUpdate: Boolean = detected.nonEmpty || unusedSet.nonEmpty + + /** update ignorance system */ + def updateIgnore: Unit = for (path <- ignore.filename) + dumpJson( + name = "algorithm names for the ignorance system", + data = mismatches.map(_.func.name).toList.sorted, + filename = path, + noSpace = false, + ) /** no sensitivity */ override val irSens: Boolean = false @@ -222,7 +220,7 @@ class TypeAnalyzer( app >> " names are not used to ignore mismatches." detected.toList.map(_.toString).sorted.map(app :> _) // show help message about how to use the ignorance system - for (path <- ignore.filename if !ignore.update) + for (path <- ignore.filename) app :> "=" * 80 app :> "To suppress this error message, " app >> "add/remove the following names to `" >> path >> "`:" @@ -235,6 +233,11 @@ class TypeAnalyzer( // private helpers // --------------------------------------------------------------------------- + /** record type mismatches */ + private def addMismatch(mismatch: TypeMismatch): Unit = + mismatchMap += mismatch.ap -> mismatch + private var mismatchMap: Map[AnalysisPoint, TypeMismatch] = Map() + /** all entry node points */ private def getNps(targets: List[Func]): List[NodePoint[Node]] = for { func <- targets @@ -289,14 +292,11 @@ class TypeAnalyzer( ) } - /** update ignorance system */ - private def updateIgnore: Unit = for (path <- ignore.filename) - dumpJson( - name = "algorithm names for the ignorance system", - data = mismatches.map(_.func.name).toList.sorted, - filename = path, - noSpace = false, - ) + /** perform type analysis */ + AbsState.setBase(new Initialize(cfg)) + transfer.fixpoint + if (log) logging + sem } object TypeAnalyzer: @@ -304,13 +304,11 @@ object TypeAnalyzer: case class Ignore( filename: Option[String] = None, names: Set[String] = Set(), - update: Boolean = false, ) object Ignore: - def apply(filename: String, update: Boolean): Ignore = Ignore( + def apply(filename: String): Ignore = Ignore( filename = Some(filename), names = optional { readJson[Set[String]](filename) }.getOrElse(Set()), - update = update, ) /** configuration for type checking */ diff --git a/src/main/scala/esmeta/phase/TypeCheck.scala b/src/main/scala/esmeta/phase/TypeCheck.scala index ed845b1c77..f2ce1fcfbf 100644 --- a/src/main/scala/esmeta/phase/TypeCheck.scala +++ b/src/main/scala/esmeta/phase/TypeCheck.scala @@ -20,7 +20,7 @@ case object TypeCheck extends Phase[CFG, TypeAnalyzer#Semantics] { config: Config, ): TypeAnalyzer#Semantics = val ignorePath = config.ignorePath.orElse(cfg.spec.manualInfo.tycheckIgnore) - val ignore = ignorePath.fold(Ignore())(Ignore(_, config.ignoreUpdate)) + val ignore = ignorePath.fold(Ignore())(Ignore.apply) val silent = cmdConfig.silent val analyzer: TypeAnalyzer = TypeAnalyzer( cfg = cfg, @@ -32,10 +32,10 @@ case object TypeCheck extends Phase[CFG, TypeAnalyzer#Semantics] { useRepl = config.useRepl, replContinue = config.replContinue, ) - val sem = analyzer.analyze - if (analyzer.detected.nonEmpty || analyzer.unusedSet.nonEmpty) + if (analyzer.needUpdate) + if (config.updateIgnore) analyzer.updateIgnore throw TypeCheckFail(if (silent) None else Some(analyzer.toString)) - sem + analyzer.sem def defaultConfig: Config = Config() val options: List[PhaseOption[Config]] = List( @@ -61,7 +61,7 @@ case object TypeCheck extends Phase[CFG, TypeAnalyzer#Semantics] { ), ( "update-ignore", - BoolOption(c => c.ignoreUpdate = true), + BoolOption(c => c.updateIgnore = true), "update the given JSON file used in ignoring type mismatches.", ), ( @@ -73,7 +73,7 @@ case object TypeCheck extends Phase[CFG, TypeAnalyzer#Semantics] { case class Config( var target: Option[String] = None, var ignorePath: Option[String] = None, - var ignoreUpdate: Boolean = false, + var updateIgnore: Boolean = false, var useRepl: Boolean = false, var replContinue: Boolean = false, var log: Boolean = false, diff --git a/src/main/scala/esmeta/util/ManualInfo.scala b/src/main/scala/esmeta/util/ManualInfo.scala index b7c8812f24..8527775733 100644 --- a/src/main/scala/esmeta/util/ManualInfo.scala +++ b/src/main/scala/esmeta/util/ManualInfo.scala @@ -42,17 +42,24 @@ case class ManualInfo(version: Option[Spec.Version]) { file <- walkTree(s"$MANUALS_DIR/$path") if filter(file.getName) } yield file - private def getPath(name: String): Option[String] = - version.fold(None)(version => { - val path = s"$MANUALS_DIR/${version.shortHash}/$name" - if (exists(path)) Some(path) else None - }) - private def getCompileRule(paths: List[String]): CompileRule = paths - .map(path => s"$MANUALS_DIR/$path/rule.json") - .map(path => optional(readJson[CompileRule](path)).getOrElse(Map())) - .foldLeft[CompileRule](Map())(_ ++ _) + private def getPath(name: String): Option[String] = for { + version <- version + tag <- version.tag + path = s"$MANUALS_DIR/$tag/$name" + if exists(path) + } yield path + private def getCompileRule(paths: List[String]): CompileRule = (for { + path <- paths + rulePath = s"$MANUALS_DIR/$path/rule.json" + rule <- optional(readJson[CompileRule](rulePath)) + } yield rule) + .foldLeft[CompileRule](Map())((acc, rule) => + rule.foldLeft(acc) { + case (acc, (k, m)) => acc + (k -> (acc.getOrElse(k, Map()) ++ m)) + }, + ) private lazy val paths: List[String] = - List("default") ++ version.map(_.shortHash) + List("default") ++ version.flatMap(_.tag) } object ManualInfo: type CompileRule = Map[String, Map[String, String]] diff --git a/src/test/scala/esmeta/analyzer/AnalyzerTest.scala b/src/test/scala/esmeta/analyzer/AnalyzerTest.scala index 0fc43901ef..02636f3629 100644 --- a/src/test/scala/esmeta/analyzer/AnalyzerTest.scala +++ b/src/test/scala/esmeta/analyzer/AnalyzerTest.scala @@ -11,7 +11,7 @@ object AnalyzerTest { import ESMetaTest.* lazy val ignorePath = cfg.spec.manualInfo.tycheckIgnore - lazy val ignore = ignorePath.fold(Ignore())(Ignore(_, update = true)) + lazy val ignore = ignorePath.fold(Ignore())(Ignore.apply) lazy val analyzer: TypeAnalyzer = TypeAnalyzer( cfg = cfg, ignore = ignore, diff --git a/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala b/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala index 01429cdebc..40e78d903c 100644 --- a/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala +++ b/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala @@ -10,6 +10,7 @@ class TypeCheckSmallTest extends AnalyzerTest { check("recent") { assert(analyzer.detected.isEmpty) assert(analyzer.unusedSet.isEmpty) + if (analyzer.needUpdate) analyzer.updateIgnore } } From e7a2d365e2f94cc48ce64dac5fc5ddeec3591656 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Fri, 9 Feb 2024 00:33:22 +0900 Subject: [PATCH 04/11] Update TypeCheckSmallTest with GitHub Actions --- .github/workflows/ci.yml | 2 +- src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala | 9 +++++---- tests/result/analyzer/TypeCheckSmallTest.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e4d59d21c..be9707b94c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - name: Run test env: ESMETA_HOME: ${{ github.workspace }} - run: sbt basicTest + run: sbt basicTest || cat tests/detail - name: Report Status if: failure() diff --git a/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala b/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala index 40e78d903c..ad2f15c060 100644 --- a/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala +++ b/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala @@ -7,10 +7,11 @@ class TypeCheckSmallTest extends AnalyzerTest { // registration def init: Unit = { - check("recent") { - assert(analyzer.detected.isEmpty) - assert(analyzer.unusedSet.isEmpty) - if (analyzer.needUpdate) analyzer.updateIgnore + check("es2023") { + if (analyzer.needUpdate) + analyzer.updateIgnore + assert(analyzer.detected.isEmpty) + assert(analyzer.unusedSet.isEmpty) } } diff --git a/tests/result/analyzer/TypeCheckSmallTest.json b/tests/result/analyzer/TypeCheckSmallTest.json index 1b0b5b856a..2838af95e3 100644 --- a/tests/result/analyzer/TypeCheckSmallTest.json +++ b/tests/result/analyzer/TypeCheckSmallTest.json @@ -1,3 +1,3 @@ { - "recent" : true + "es2023" : true } From 610606912a8a4e4e827363fe18ca60ef8fc284e5 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Fri, 9 Feb 2024 01:51:56 +0900 Subject: [PATCH 05/11] Fix error in workflows and update hashcode for each version --- .github/workflows/ci.yml | 13 ++++++++++--- src/main/resources/manuals/es2022/hash | 1 + src/main/resources/manuals/es2022/tag | 1 - src/main/resources/manuals/es2023/hash | 1 + src/main/resources/manuals/es2023/tag | 1 - 5 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/manuals/es2022/hash delete mode 100644 src/main/resources/manuals/es2022/tag create mode 100644 src/main/resources/manuals/es2023/hash delete mode 100644 src/main/resources/manuals/es2023/tag diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be9707b94c..bd5af1efaf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,14 +10,21 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: + fetch-tags: true submodules: true + - name: Fetch all commits and tags from ecma262 + env: + ESMETA_HOME: ${{ github.workspace }} + run: + cd ecma262 && git fetch --all --tags + - name: Run test env: ESMETA_HOME: ${{ github.workspace }} - run: sbt basicTest || cat tests/detail + run: sbt basicTest || (cat tests/detail && exit 1) - - name: Report Status + - name: Report status if: failure() uses: 8398a7/action-slack@v3 with: @@ -35,7 +42,7 @@ jobs: - name: Check code format run: sbt formatCheck - - name: Report Status + - name: Report status if: failure() uses: 8398a7/action-slack@v3 with: diff --git a/src/main/resources/manuals/es2022/hash b/src/main/resources/manuals/es2022/hash new file mode 100644 index 0000000000..bda534e1fb --- /dev/null +++ b/src/main/resources/manuals/es2022/hash @@ -0,0 +1 @@ +d711ba960cd12b7658d6bb26d7556e690290190c diff --git a/src/main/resources/manuals/es2022/tag b/src/main/resources/manuals/es2022/tag deleted file mode 100644 index d4449e8332..0000000000 --- a/src/main/resources/manuals/es2022/tag +++ /dev/null @@ -1 +0,0 @@ -es2022 diff --git a/src/main/resources/manuals/es2023/hash b/src/main/resources/manuals/es2023/hash new file mode 100644 index 0000000000..577b182b6e --- /dev/null +++ b/src/main/resources/manuals/es2023/hash @@ -0,0 +1 @@ +d048f32e861c2ed4a26f59a50d392918f26da3ba diff --git a/src/main/resources/manuals/es2023/tag b/src/main/resources/manuals/es2023/tag deleted file mode 100644 index de872efbb3..0000000000 --- a/src/main/resources/manuals/es2023/tag +++ /dev/null @@ -1 +0,0 @@ -es2023 From 73aa25800655ec9bdf8336565db9ea7fb25cc436 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Fri, 9 Feb 2024 02:41:57 +0900 Subject: [PATCH 06/11] Add tests for type analysis of recent spec --- .../resources/manuals/default/tycheck-ignore.json | 13 +++---------- src/main/scala/esmeta/Command.scala | 3 +++ src/main/scala/esmeta/analyzer/TypeAnalyzer.scala | 12 ++++++------ src/main/scala/esmeta/phase/TypeCheck.scala | 1 + src/main/scala/esmeta/util/Git.scala | 9 ++++++--- src/main/scala/esmeta/util/ManualInfo.scala | 10 ++++++++++ src/test/scala/esmeta/ESMetaTest.scala | 6 +++++- src/test/scala/esmeta/analyzer/AnalyzerTest.scala | 15 ++++++++++++--- .../esmeta/analyzer/TypeCheckSmallTest.scala | 14 ++++++++------ tests/result/analyzer/TypeCheckSmallTest | 2 +- tests/result/analyzer/TypeCheckSmallTest.json | 1 + 11 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/main/resources/manuals/default/tycheck-ignore.json b/src/main/resources/manuals/default/tycheck-ignore.json index fe5f1a8961..8110956d57 100644 --- a/src/main/resources/manuals/default/tycheck-ignore.json +++ b/src/main/resources/manuals/default/tycheck-ignore.json @@ -11,18 +11,12 @@ "ArrayAssignmentPattern[2,3].DestructuringAssignmentEvaluation", "ArrayCreate", "AsyncGeneratorBody[0,0].EvaluateAsyncGeneratorBody", - "AsyncGeneratorUnwrapYieldResumption", "BindingPattern[1,0].BindingInitialization", "BoundFunctionCreate", "Catch[0,0].CatchClauseEvaluation", "Catch[1,0].CatchClauseEvaluation", "ClassStaticBlockBody[0,0].EvaluateClassStaticBlockBody", - "CreateArrayIterator", "CreateBuiltinFunction", - "CreateIterResultObject", - "ECMAScriptFunctionObject.Call", - "ECMAScriptFunctionObject.Construct", - "FindViaPredicate", "FlattenIntoArray", "ForIn/OfBodyEvaluation", "FunctionBody[0,0].EvaluateFunctionBody", @@ -31,22 +25,21 @@ "GetMethod", "GetPromiseResolve", "GetThisValue", - "INTRINSICS.DefaultTimeZone", "InnerModuleEvaluation", - "IntegerIndexedObjectCreate", "LabelledItem[1,0].LabelledEvaluation", "LengthOfArrayLike", "MethodDefinition[0,0].DefineMethod", "ModuleNamespaceCreate", "ProxyCreate", + "ScriptEvaluation", "SerializeJSONObject", "SetFunctionLength", - "SortIndexedProperties", "SourceTextModuleRecord.ExecuteModule", "SourceTextModuleRecord.ResolveExport", "SpeciesConstructor", "Statement[0,0].LabelledEvaluation", "Statement[1,0].LabelledEvaluation", "Statement[2,0].LabelledEvaluation", - "StringCreate" + "StringCreate", + "StringPad" ] \ No newline at end of file diff --git a/src/main/scala/esmeta/Command.scala b/src/main/scala/esmeta/Command.scala index 7c7e2607a7..b9a0a129a8 100644 --- a/src/main/scala/esmeta/Command.scala +++ b/src/main/scala/esmeta/Command.scala @@ -40,6 +40,9 @@ sealed abstract class Command[Result]( parser(args) ESMeta(this, runner(_), cmdConfig) + /** run command with command-line arguments */ + def apply(args: String): Result = apply(args.split(" +").toList) + /** a list of phases without specific IO types */ def phases: Vector[Phase[_, _]] = pList.phases diff --git a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala index cf425f9c98..726bed8c0a 100644 --- a/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala +++ b/src/main/scala/esmeta/analyzer/TypeAnalyzer.scala @@ -26,6 +26,12 @@ class TypeAnalyzer( ) extends Analyzer { import TypeAnalyzer.* + /** perform type analysis */ + lazy val analyze: Unit = + AbsState.setBase(new Initialize(cfg)) + transfer.fixpoint + if (log) logging + /** unused ignore set */ protected var _unusedSet: Set[String] = ignore.names inline def unusedSet: Set[String] = _unusedSet @@ -291,12 +297,6 @@ class TypeAnalyzer( filename = s"$ANALYZE_LOG_DIR/mismatches", ) } - - /** perform type analysis */ - AbsState.setBase(new Initialize(cfg)) - transfer.fixpoint - if (log) logging - sem } object TypeAnalyzer: diff --git a/src/main/scala/esmeta/phase/TypeCheck.scala b/src/main/scala/esmeta/phase/TypeCheck.scala index f2ce1fcfbf..c4cd01cc0f 100644 --- a/src/main/scala/esmeta/phase/TypeCheck.scala +++ b/src/main/scala/esmeta/phase/TypeCheck.scala @@ -32,6 +32,7 @@ case object TypeCheck extends Phase[CFG, TypeAnalyzer#Semantics] { useRepl = config.useRepl, replContinue = config.replContinue, ) + analyzer.analyze if (analyzer.needUpdate) if (config.updateIgnore) analyzer.updateIgnore throw TypeCheckFail(if (silent) None else Some(analyzer.toString)) diff --git a/src/main/scala/esmeta/util/Git.scala b/src/main/scala/esmeta/util/Git.scala index 609e35e59f..5beba40706 100644 --- a/src/main/scala/esmeta/util/Git.scala +++ b/src/main/scala/esmeta/util/Git.scala @@ -44,9 +44,12 @@ abstract class Git(path: String, shortHashLength: Int = 16) { self => executeCmd(s"git rev-list -n 1 $target", path).trim /** get git tag */ - def getTag(target: String): Option[String] = optional( - executeCmd(s"git describe --tags --exact-match $target", path).trim, - ) + def getTag(target: String): Option[String] = optional { + executeCmd( + s"git describe --tags --exact-match $target 2>/dev/null", + path, + ).trim + } /** apply git patch */ def applyPatch(patch: String): Unit = executeCmd(s"patch -p1 < $patch", path) diff --git a/src/main/scala/esmeta/util/ManualInfo.scala b/src/main/scala/esmeta/util/ManualInfo.scala index 8527775733..f57372bde8 100644 --- a/src/main/scala/esmeta/util/ManualInfo.scala +++ b/src/main/scala/esmeta/util/ManualInfo.scala @@ -63,3 +63,13 @@ case class ManualInfo(version: Option[Spec.Version]) { } object ManualInfo: type CompileRule = Map[String, Map[String, String]] + + /** default path */ + lazy val defaultPath: String = s"$MANUALS_DIR/default" + + /** default version */ + lazy val defaultVersion: Spec.Version = + Spec.getVersion(readFile(s"$defaultPath/hash")) + + /** default tycheck-ignore.json */ + lazy val tycheckIgnore: String = s"$defaultPath/tycheck-ignore.json" diff --git a/src/test/scala/esmeta/ESMetaTest.scala b/src/test/scala/esmeta/ESMetaTest.scala index 1f2ce63528..037bfd228d 100644 --- a/src/test/scala/esmeta/ESMetaTest.scala +++ b/src/test/scala/esmeta/ESMetaTest.scala @@ -1,11 +1,13 @@ package esmeta +import esmeta.cfg.CFG import esmeta.cfgBuilder.CFGBuilder import esmeta.compiler.Compiler import esmeta.error.NotSupported.* import esmeta.error.{NotSupported => NSError} import esmeta.extractor.Extractor import esmeta.phase.* +import esmeta.util.* import esmeta.util.BaseUtils.* import esmeta.util.BasicParsers import esmeta.util.SystemUtils.* @@ -203,9 +205,11 @@ trait ESMetaTest extends funsuite.AnyFunSuite with BeforeAndAfterAll { } object ESMetaTest { // extract specifications - lazy val specOpt = optional(Extractor()) lazy val spec = Extractor() lazy val grammar = spec.grammar lazy val program = Compiler(spec) lazy val cfg = CFGBuilder(program) + lazy val defaultVersion = ManualInfo.defaultVersion + lazy val defaultCFG = getCFG(defaultVersion.hash) + def getCFG(target: String): CFG = CFGBuilder(Compiler(Extractor(target))) } diff --git a/src/test/scala/esmeta/analyzer/AnalyzerTest.scala b/src/test/scala/esmeta/analyzer/AnalyzerTest.scala index 02636f3629..aa1e9f19e3 100644 --- a/src/test/scala/esmeta/analyzer/AnalyzerTest.scala +++ b/src/test/scala/esmeta/analyzer/AnalyzerTest.scala @@ -2,6 +2,8 @@ package esmeta.analyzer import esmeta.ESMetaTest import esmeta.analyzer.TypeAnalyzer.Ignore +import esmeta.cfg.CFG +import esmeta.util.* /** analyzer tests */ trait AnalyzerTest extends ESMetaTest { @@ -9,10 +11,17 @@ trait AnalyzerTest extends ESMetaTest { } object AnalyzerTest { import ESMetaTest.* + lazy val analyzer: TypeAnalyzer = getAnalyzer(cfg) + lazy val defaultIgnore: Ignore = Ignore(ManualInfo.tycheckIgnore) + lazy val defaultAnalyzer: TypeAnalyzer = + getAnalyzer(defaultCFG, defaultIgnore) - lazy val ignorePath = cfg.spec.manualInfo.tycheckIgnore - lazy val ignore = ignorePath.fold(Ignore())(Ignore.apply) - lazy val analyzer: TypeAnalyzer = TypeAnalyzer( + // helper methods + def getIgnore(cfg: CFG): Ignore = + cfg.spec.manualInfo.tycheckIgnore.fold(Ignore())(Ignore.apply) + def getAnalyzer(target: String): TypeAnalyzer = getAnalyzer(getCFG(target)) + def getAnalyzer(cfg: CFG): TypeAnalyzer = getAnalyzer(cfg, getIgnore(cfg)) + def getAnalyzer(cfg: CFG, ignore: Ignore): TypeAnalyzer = TypeAnalyzer( cfg = cfg, ignore = ignore, silent = true, diff --git a/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala b/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala index ad2f15c060..342014bed2 100644 --- a/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala +++ b/src/test/scala/esmeta/analyzer/TypeCheckSmallTest.scala @@ -5,14 +5,16 @@ class TypeCheckSmallTest extends AnalyzerTest { val name: String = "analyzerTypeCheckTest" + def checkAnalyzer(analyzer: TypeAnalyzer): Unit = + analyzer.analyze + if (analyzer.needUpdate) + analyzer.updateIgnore + fail(analyzer.toString) + // registration def init: Unit = { - check("es2023") { - if (analyzer.needUpdate) - analyzer.updateIgnore - assert(analyzer.detected.isEmpty) - assert(analyzer.unusedSet.isEmpty) - } + check("es2023") { checkAnalyzer(analyzer) } + check("default") { checkAnalyzer(defaultAnalyzer) } } init diff --git a/tests/result/analyzer/TypeCheckSmallTest b/tests/result/analyzer/TypeCheckSmallTest index 55ce4b4566..ae30425dc5 100644 --- a/tests/result/analyzer/TypeCheckSmallTest +++ b/tests/result/analyzer/TypeCheckSmallTest @@ -1 +1 @@ -analyzer.TypeCheckSmallTest: 1 / 1 +analyzer.TypeCheckSmallTest: 2 / 2 diff --git a/tests/result/analyzer/TypeCheckSmallTest.json b/tests/result/analyzer/TypeCheckSmallTest.json index 2838af95e3..dd2cc98fa1 100644 --- a/tests/result/analyzer/TypeCheckSmallTest.json +++ b/tests/result/analyzer/TypeCheckSmallTest.json @@ -1,3 +1,4 @@ { + "default" : true, "es2023" : true } From 3f8aa0d7a5098f472c3b1b5c6c2577b7225c3798 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Fri, 9 Feb 2024 02:55:21 +0900 Subject: [PATCH 07/11] Update executeCmd system util --- src/main/resources/manuals/default/tycheck-ignore.json | 1 + src/main/scala/esmeta/util/SystemUtils.scala | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/resources/manuals/default/tycheck-ignore.json b/src/main/resources/manuals/default/tycheck-ignore.json index 8110956d57..d51b7bc507 100644 --- a/src/main/resources/manuals/default/tycheck-ignore.json +++ b/src/main/resources/manuals/default/tycheck-ignore.json @@ -17,6 +17,7 @@ "Catch[1,0].CatchClauseEvaluation", "ClassStaticBlockBody[0,0].EvaluateClassStaticBlockBody", "CreateBuiltinFunction", + "CreateDynamicFunction", "FlattenIntoArray", "ForIn/OfBodyEvaluation", "FunctionBody[0,0].EvaluateFunctionBody", diff --git a/src/main/scala/esmeta/util/SystemUtils.scala b/src/main/scala/esmeta/util/SystemUtils.scala index 82e9026e3e..d24f941be6 100644 --- a/src/main/scala/esmeta/util/SystemUtils.scala +++ b/src/main/scala/esmeta/util/SystemUtils.scala @@ -171,11 +171,12 @@ object SystemUtils { def isNormalExit(str: String): Boolean = optional(executeCmd(str)).isDefined /** execute shell command with given dir, default to CUR_DIR */ - def executeCmd(cmdStr: String, dir: String = CUR_DIR): String = - var cmd = s"$cmdStr 2> /dev/null" + def executeCmd(cmd: String, dir: String = CUR_DIR): String = var directory = File(dir) var process = Process(Seq("sh", "-c", cmd), directory) - process.!! + val sb = new StringBuilder + process ! ProcessLogger(sb.append, _ => ()) + sb.toString /** set timeout with optional limitation */ def timeout[T](f: => T, limit: Option[Int]): T = From ceafb691a8dd307367e710454c2bff78798841f7 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Fri, 9 Feb 2024 04:22:41 +0900 Subject: [PATCH 08/11] Fix error in command execution helper --- src/main/resources/manuals/default/tycheck-ignore.json | 1 - src/main/scala/esmeta/util/SystemUtils.scala | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/resources/manuals/default/tycheck-ignore.json b/src/main/resources/manuals/default/tycheck-ignore.json index d51b7bc507..8110956d57 100644 --- a/src/main/resources/manuals/default/tycheck-ignore.json +++ b/src/main/resources/manuals/default/tycheck-ignore.json @@ -17,7 +17,6 @@ "Catch[1,0].CatchClauseEvaluation", "ClassStaticBlockBody[0,0].EvaluateClassStaticBlockBody", "CreateBuiltinFunction", - "CreateDynamicFunction", "FlattenIntoArray", "ForIn/OfBodyEvaluation", "FunctionBody[0,0].EvaluateFunctionBody", diff --git a/src/main/scala/esmeta/util/SystemUtils.scala b/src/main/scala/esmeta/util/SystemUtils.scala index d24f941be6..7a096fb145 100644 --- a/src/main/scala/esmeta/util/SystemUtils.scala +++ b/src/main/scala/esmeta/util/SystemUtils.scala @@ -175,8 +175,7 @@ object SystemUtils { var directory = File(dir) var process = Process(Seq("sh", "-c", cmd), directory) val sb = new StringBuilder - process ! ProcessLogger(sb.append, _ => ()) - sb.toString + process !! ProcessLogger(s => (), s => ()) /** set timeout with optional limitation */ def timeout[T](f: => T, limit: Option[Int]): T = From 2660c41ae9f554913e19f45caa0493636c638685 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Fri, 9 Feb 2024 04:29:58 +0900 Subject: [PATCH 09/11] Update client submodule and related CI workflow --- .github/workflows/e2e.yml | 2 +- client | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 257c56e557..a307a8fc04 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -23,7 +23,7 @@ jobs: if: always() with: name: playwright-report - path: playwright-report/ + path: $ESMETA_HOME/client/playwright-report/ retention-days: 30 - name: Report Status diff --git a/client b/client index 43be3c13d9..36d0ad5289 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 43be3c13d9ce8fa670028b42537535245f055140 +Subproject commit 36d0ad5289354d42ceef8393cab9293c421fdf99 From 822378fdfa0e80b1cad6076d11daebfa815e1571 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Fri, 9 Feb 2024 04:41:40 +0900 Subject: [PATCH 10/11] Remove unnecesary `fetch-tags` option in `ci.yml` --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd5af1efaf..cfa1b4c52a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - fetch-tags: true submodules: true - name: Fetch all commits and tags from ecma262 From 1719ef5f243d18811d8aca37462ad2960d38dd11 Mon Sep 17 00:00:00 2001 From: Jihyeok Park Date: Fri, 9 Feb 2024 04:46:28 +0900 Subject: [PATCH 11/11] Update ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfa1b4c52a..880a0dd38f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,11 +12,11 @@ jobs: with: submodules: true - - name: Fetch all commits and tags from ecma262 + - name: Fetch all tags from ecma262 env: ESMETA_HOME: ${{ github.workspace }} run: - cd ecma262 && git fetch --all --tags + cd ecma262 && git fetch --tags - name: Run test env: