From d14eb855fc88e086b587f6c9695b59eb230c79e7 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Fri, 8 Nov 2019 12:14:43 +0000 Subject: [PATCH] Go analysis support for CodeQL. --- .codeqlmanifest.json | 3 + .gitignore | 21 + CODE_OF_CONDUCT.md | 76 + CONTRIBUTING.md | 44 + COPYRIGHT | 13 + LICENSE | 21 + Makefile | 61 + README.md | 45 + SECURITY.md | 3 + alert_weighting.properties | 3 + build/.gitkeep | 0 codeql-extractor.yml | 16 + codeql-tools/autobuild.cmd | 10 + codeql-tools/autobuild.sh | 14 + .../cli/go-autobuilder/go-autobuilder.go | 350 + .../cli/go-autobuilder/go-autobuilder_test.go | 19 + extractor/cli/go-bootstrap/go-bootstrap.go | 51 + extractor/cli/go-extractor/go-extractor.go | 71 + extractor/cli/go-tokenizer/go-tokenizer.go | 57 + extractor/dbscheme/dbscheme.go | 412 + extractor/dbscheme/tables.go | 872 ++ extractor/extractor.go | 1305 ++ .../sourceforge/pmd/cpd/AbstractLanguage.java | 13 + .../net/sourceforge/pmd/cpd/GoLanguage.java | 68 + .../net/sourceforge/pmd/cpd/SourceCode.java | 12 + .../net/sourceforge/pmd/cpd/TokenEntry.java | 11 + .../net/sourceforge/pmd/cpd/Tokenizer.java | 12 + extractor/opencsv/CSVParser.java | 207 + extractor/opencsv/CSVReader.java | 192 + extractor/semaphore.go | 42 + extractor/srcarchive/projectlayout.go | 105 + extractor/srcarchive/projectlayout_test.go | 136 + extractor/srcarchive/srcarchive.go | 82 + extractor/trap/labels.go | 216 + extractor/trap/trapwriter.go | 106 + extractor/trap/util.go | 9 + go.mod | 5 + go.sum | 9 + ql/config/legacy-support/qlpack.yml | 3 + ql/config/suites/lgtm/go-alerts-lgtm | 3 + ql/config/suites/lgtm/go-lgtm | 3 + ql/config/suites/lgtm/go-metrics-lgtm | 2 + ql/config/suites/lgtm/go-util-lgtm | 6 + ql/src/.project | 12 + ql/src/.qlpath | 5 + ql/src/AlertSuppression.ql | 78 + ql/src/Customizations.qll | 12 + .../InconsistentLoopOrientation.go | 13 + .../InconsistentLoopOrientation.qhelp | 53 + .../InconsistentLoopOrientation.ql | 55 + .../InconsistentLoopOrientationGood.go | 13 + .../LengthComparisonOffByOne.go | 15 + .../LengthComparisonOffByOne.qhelp | 47 + .../LengthComparisonOffByOne.ql | 100 + .../LengthComparisonOffByOneGood.go | 14 + .../MistypedExponentiation.go | 7 + .../MistypedExponentiation.qhelp | 36 + .../MistypedExponentiation.ql | 37 + .../WhitespaceContradictsPrecedence.go | 5 + .../WhitespaceContradictsPrecedence.qhelp | 44 + .../WhitespaceContradictsPrecedence.ql | 88 + .../WhitespaceContradictsPrecedenceGood.go | 5 + ql/src/Metrics/FLinesOfCode.qhelp | 38 + ql/src/Metrics/FLinesOfCode.ql | 18 + ql/src/Metrics/FLinesOfComment.qhelp | 24 + ql/src/Metrics/FLinesOfComment.ql | 17 + ql/src/RedundantCode/Clones.qll | 166 + .../RedundantCode/CompareIdenticalValues.go | 12 + .../CompareIdenticalValues.qhelp | 37 + .../RedundantCode/CompareIdenticalValues.ql | 27 + .../CompareIdenticalValuesGood.go | 8 + ql/src/RedundantCode/DeadStoreOfField.go | 9 + ql/src/RedundantCode/DeadStoreOfField.qhelp | 42 + ql/src/RedundantCode/DeadStoreOfField.ql | 66 + ql/src/RedundantCode/DeadStoreOfFieldGood.go | 5 + ql/src/RedundantCode/DeadStoreOfLocal.qhelp | 45 + ql/src/RedundantCode/DeadStoreOfLocal.ql | 42 + ql/src/RedundantCode/DeadStoreOfLocalBad.go | 19 + ql/src/RedundantCode/DeadStoreOfLocalGood.go | 16 + ql/src/RedundantCode/DuplicateBranches.go | 9 + ql/src/RedundantCode/DuplicateBranches.qhelp | 36 + ql/src/RedundantCode/DuplicateBranches.ql | 25 + ql/src/RedundantCode/DuplicateBranchesGood.go | 9 + ql/src/RedundantCode/DuplicateCondition.go | 11 + ql/src/RedundantCode/DuplicateCondition.qhelp | 38 + ql/src/RedundantCode/DuplicateCondition.ql | 33 + .../RedundantCode/DuplicateConditionGood.go | 11 + ql/src/RedundantCode/DuplicateSwitchCase.go | 12 + .../RedundantCode/DuplicateSwitchCase.qhelp | 38 + ql/src/RedundantCode/DuplicateSwitchCase.ql | 23 + .../RedundantCode/DuplicateSwitchCaseGood.go | 12 + ql/src/RedundantCode/ExprHasNoEffect.go | 15 + ql/src/RedundantCode/ExprHasNoEffect.qhelp | 37 + ql/src/RedundantCode/ExprHasNoEffect.ql | 39 + ql/src/RedundantCode/ExprHasNoEffectGood.go | 9 + ql/src/RedundantCode/NegativeLengthCheck.go | 8 + .../RedundantCode/NegativeLengthCheck.qhelp | 45 + ql/src/RedundantCode/NegativeLengthCheck.ql | 36 + .../RedundantCode/NegativeLengthCheckGood.go | 8 + ql/src/RedundantCode/RedundantExpr.go | 5 + ql/src/RedundantCode/RedundantExpr.qhelp | 36 + ql/src/RedundantCode/RedundantExpr.ql | 64 + ql/src/RedundantCode/RedundantExprGood.go | 5 + ql/src/RedundantCode/SelfAssignment.go | 13 + ql/src/RedundantCode/SelfAssignment.qhelp | 41 + ql/src/RedundantCode/SelfAssignment.ql | 26 + ql/src/RedundantCode/SelfAssignmentGood.go | 5 + ql/src/RedundantCode/ShiftOutOfRange.go | 7 + ql/src/RedundantCode/ShiftOutOfRange.qhelp | 39 + ql/src/RedundantCode/ShiftOutOfRange.ql | 23 + ql/src/RedundantCode/ShiftOutOfRangeGood.go | 7 + ql/src/RedundantCode/UnreachableStatement.go | 13 + .../RedundantCode/UnreachableStatement.qhelp | 36 + ql/src/RedundantCode/UnreachableStatement.ql | 53 + .../RedundantCode/UnreachableStatementGood.go | 13 + .../CWE-020/IncompleteHostnameRegexp.go | 16 + .../CWE-020/IncompleteHostnameRegexp.qhelp | 50 + .../CWE-020/IncompleteHostnameRegexp.ql | 56 + .../CWE-020/IncompleteHostnameRegexpGood.go | 16 + .../Security/CWE-020/MissingRegexpAnchor.go | 16 + .../CWE-020/MissingRegexpAnchor.qhelp | 54 + .../Security/CWE-020/MissingRegexpAnchor.ql | 77 + .../CWE-020/MissingRegexpAnchorGood.go | 16 + ql/src/Security/CWE-022/TaintedPath.go | 19 + ql/src/Security/CWE-022/TaintedPath.qhelp | 51 + ql/src/Security/CWE-022/TaintedPath.ql | 24 + ql/src/Security/CWE-022/ZipSlip.go | 16 + ql/src/Security/CWE-022/ZipSlip.qhelp | 69 + ql/src/Security/CWE-022/ZipSlip.ql | 22 + ql/src/Security/CWE-022/ZipSlipGood.go | 19 + ql/src/Security/CWE-078/CommandInjection.go | 12 + .../Security/CWE-078/CommandInjection.qhelp | 41 + ql/src/Security/CWE-078/CommandInjection.ql | 20 + ql/src/Security/CWE-079/ReflectedXss.go | 20 + ql/src/Security/CWE-079/ReflectedXss.qhelp | 52 + ql/src/Security/CWE-079/ReflectedXss.ql | 21 + ql/src/Security/CWE-079/ReflectedXssGood.go | 21 + ql/src/Security/CWE-089/SqlInjection.go | 13 + ql/src/Security/CWE-089/SqlInjection.qhelp | 43 + ql/src/Security/CWE-089/SqlInjection.ql | 20 + ql/src/Security/CWE-089/SqlInjectionGood.go | 11 + ql/src/Security/CWE-312/CleartextLogging.go | 17 + .../Security/CWE-312/CleartextLogging.qhelp | 50 + ql/src/Security/CWE-312/CleartextLogging.ql | 22 + .../Security/CWE-312/CleartextLoggingGood.go | 19 + ql/src/Security/CWE-601/OpenUrlRedirect.go | 12 + ql/src/Security/CWE-601/OpenUrlRedirect.qhelp | 42 + ql/src/Security/CWE-601/OpenUrlRedirect.ql | 20 + .../Security/CWE-601/OpenUrlRedirectGood.go | 23 + .../Security/CWE-798/HardcodedCredentials.go | 24 + .../CWE-798/HardcodedCredentials.qhelp | 45 + .../Security/CWE-798/HardcodedCredentials.ql | 57 + ql/src/codeql-suites/go-lgtm-full.qls | 4 + ql/src/codeql-suites/go-lgtm.qls | 4 + ql/src/definitions.ql | 15 + ql/src/filters/ClassifyFiles.ql | 45 + ql/src/go.dbscheme | 402 + ql/src/go.dbscheme.stats | 11138 ++++++++++++++++ ql/src/go.qll | 30 + ql/src/qlpack.yml | 4 + ql/src/queries.xml | 1 + ql/src/semmle/go/AST.qll | 134 + ql/src/semmle/go/Comments.qll | 110 + ql/src/semmle/go/Concepts.qll | 595 + ql/src/semmle/go/Decls.qll | 594 + ql/src/semmle/go/Expr.qll | 1282 ++ ql/src/semmle/go/Files.qll | 209 + ql/src/semmle/go/Locations.qll | 81 + ql/src/semmle/go/Packages.qll | 22 + ql/src/semmle/go/Scopes.qll | 581 + ql/src/semmle/go/Stmt.qll | 628 + ql/src/semmle/go/StringConcatenation.qll | 59 + ql/src/semmle/go/Types.qll | 592 + ql/src/semmle/go/Util.qll | 18 + ql/src/semmle/go/controlflow/BasicBlocks.qll | 199 + .../go/controlflow/ControlFlowGraph.qll | 227 + .../go/controlflow/ControlFlowGraphImpl.qll | 1847 +++ ql/src/semmle/go/controlflow/IR.qll | 1394 ++ ql/src/semmle/go/dataflow/DataFlow.qll | 25 + .../go/dataflow/FunctionInputsAndOutputs.qll | 178 + .../go/dataflow/GlobalValueNumbering.qll | 582 + ql/src/semmle/go/dataflow/Properties.qll | 60 + ql/src/semmle/go/dataflow/SSA.qll | 285 + ql/src/semmle/go/dataflow/SsaImpl.qll | 279 + ql/src/semmle/go/dataflow/TaintTracking.qll | 128 + .../go/dataflow/internal/DataFlowDispatch.qll | 38 + .../go/dataflow/internal/DataFlowImpl.qll | 1864 +++ .../dataflow/internal/DataFlowImplCommon.qll | 469 + .../internal/DataFlowImplSpecific.qll | 11 + .../go/dataflow/internal/DataFlowPrivate.qll | 243 + .../go/dataflow/internal/DataFlowUtil.qll | 889 ++ ql/src/semmle/go/frameworks/Glog.qll | 27 + ql/src/semmle/go/frameworks/HTTP.qll | 149 + ql/src/semmle/go/frameworks/Logrus.qll | 60 + ql/src/semmle/go/frameworks/SQL.qll | 71 + ql/src/semmle/go/frameworks/Stdlib.qll | 533 + .../go/frameworks/SystemCommandExecutors.qll | 35 + .../semmle/go/security/CleartextLogging.qll | 52 + .../CleartextLoggingCustomizations.qll | 142 + .../semmle/go/security/CommandInjection.qll | 32 + .../CommandInjectionCustomizations.qll | 39 + ql/src/semmle/go/security/FlowSources.qll | 27 + ql/src/semmle/go/security/OpenUrlRedirect.qll | 58 + .../OpenUrlRedirectCustomizations.qll | 113 + ql/src/semmle/go/security/ReflectedXss.qll | 28 + .../security/ReflectedXssCustomizations.qll | 82 + .../semmle/go/security/SensitiveActions.qll | 250 + ql/src/semmle/go/security/SqlInjection.qll | 30 + .../security/SqlInjectionCustomizations.qll | 38 + ql/src/semmle/go/security/TaintedPath.qll | 30 + .../go/security/TaintedPathCustomizations.qll | 146 + .../semmle/go/security/UrlConcatenation.qll | 77 + ql/src/semmle/go/security/ZipSlip.qll | 30 + .../go/security/ZipSlipCustomizations.qll | 61 + ql/test/.project | 12 + ql/test/.qlpath | 10 + .../extractor-tests/go1.13/literals.expected | 9 + ql/test/extractor-tests/go1.13/literals.ql | 4 + ql/test/extractor-tests/go1.13/tst.go | 13 + .../extractor-tests/robustness/tst.expected | 1 + ql/test/extractor-tests/robustness/tst.go | 7 + ql/test/extractor-tests/robustness/tst.ql | 3 + .../semmle/go/Expr/BasicLit_getText.expected | 74 + .../semmle/go/Expr/BasicLit_getText.ql | 4 + .../semmle/go/Expr/BasicLit_getValue.expected | 74 + .../semmle/go/Expr/BasicLit_getValue.ql | 4 + .../semmle/go/Expr/CompositeLit.expected | 43 + .../semmle/go/Expr/CompositeLit.ql | 8 + .../semmle/go/Expr/ConstantValues.expected | 29 + .../semmle/go/Expr/ConstantValues.ql | 31 + .../semmle/go/Expr/Ident.expected | 80 + ql/test/library-tests/semmle/go/Expr/Ident.ql | 4 + .../library-tests/semmle/go/Expr/consts.go | 49 + .../library-tests/semmle/go/Expr/literals.go | 67 + .../semmle/go/Scopes/DeclaredEntity.expected | 20 + .../semmle/go/Scopes/DeclaredEntity.ql | 4 + .../semmle/go/Scopes/EntityType.expected | 20 + .../semmle/go/Scopes/EntityType.ql | 5 + .../semmle/go/Scopes/EntityUse.expected | 30 + .../semmle/go/Scopes/EntityUse.ql | 9 + .../go/Scopes/MethodImplements.expected | 6 + .../semmle/go/Scopes/MethodImplements.ql | 5 + .../go/Scopes/MethodImplementsName.expected | 6 + .../semmle/go/Scopes/MethodImplementsName.ql | 7 + .../semmle/go/Scopes/Methods.expected | 8 + .../library-tests/semmle/go/Scopes/Methods.ql | 5 + .../semmle/go/Scopes/TypeImplements.expected | 6 + .../semmle/go/Scopes/TypeImplements.ql | 5 + .../library-tests/semmle/go/Scopes/main.go | 21 + .../library-tests/semmle/go/Scopes/types.go | 30 + .../semmle/go/Types/MethodDecls.expected | 1 + .../semmle/go/Types/MethodDecls.ql | 4 + .../semmle/go/Types/Methods.expected | 3 + .../library-tests/semmle/go/Types/Methods.ql | 7 + .../semmle/go/Types/QualifiedNames.expected | 8 + .../semmle/go/Types/QualifiedNames.ql | 5 + .../SignatureType_getNumParameter.expected | 6 + .../go/Types/SignatureType_getNumParameter.ql | 4 + .../Types/SignatureType_getNumResult.expected | 6 + .../go/Types/SignatureType_getNumResult.ql | 4 + .../semmle/go/Types/StructFields.expected | 21 + .../semmle/go/Types/StructFields.ql | 7 + .../semmle/go/Types/Types.expected | 8 + .../library-tests/semmle/go/Types/Types.ql | 5 + ql/test/library-tests/semmle/go/Types/main.go | 11 + .../library-tests/semmle/go/Types/pkg1/tst.go | 57 + .../library-tests/semmle/go/Types/pkg2/tst.go | 9 + .../EscapeFunction/EscapeFunction.expected | 7 + .../concepts/EscapeFunction/EscapeFunction.go | 14 + .../concepts/EscapeFunction/EscapeFunction.ql | 4 + .../Regexp/RegexpMatchFunction.expected | 3 + .../go/concepts/Regexp/RegexpMatchFunction.ql | 5 + .../go/concepts/Regexp/RegexpPattern.expected | 22 + .../go/concepts/Regexp/RegexpPattern.ql | 4 + .../Regexp/RegexpReplaceFunction.expected | 6 + .../concepts/Regexp/RegexpReplaceFunction.ql | 5 + .../semmle/go/concepts/Regexp/stdlib.go | 32 + .../SystemCommandExecution.expected | 5 + .../SystemCommandExecution.ql | 4 + .../go/concepts/SystemCommandExecution/go.mod | 5 + .../concepts/SystemCommandExecution/gosh.go | 7 + .../concepts/SystemCommandExecution/main.go | 22 + .../github.com/codeskyblue/go-sh/LICENSE | 202 + .../github.com/codeskyblue/go-sh/README.md | 3 + .../vendor/github.com/codeskyblue/go-sh/sh.go | 9 + .../SystemCommandExecution/vendor/modules.txt | 2 + .../go/concepts/Templates/Templates.expected | 2 + .../semmle/go/concepts/Templates/Templates.ql | 4 + .../semmle/go/concepts/Templates/main.go | 33 + .../controlflow/ControlFlowGraph/CFG.expected | 4 + .../go/controlflow/ControlFlowGraph/CFG.ql | 10 + .../ControlFlowNode_getASuccessor.expected | 1167 ++ .../ControlFlowNode_getASuccessor.ql | 7 + .../go/controlflow/ControlFlowGraph/exprs.go | 95 + .../go/controlflow/ControlFlowGraph/hello.go | 9 + .../go/controlflow/ControlFlowGraph/linux.go | 5 + .../go/controlflow/ControlFlowGraph/main.go | 86 + .../controlflow/ControlFlowGraph/nonlinux.go | 5 + .../go/controlflow/ControlFlowGraph/stmts.go | 145 + .../go/controlflow/ControlFlowGraph/stmts2.go | 28 + .../go/controlflow/ControlFlowGraph/stmts3.go | 20 + .../go/controlflow/ControlFlowGraph/stmts4.go | 5 + .../go/controlflow/ControlFlowGraph/stmts5.go | 12 + .../go/controlflow/ControlFlowGraph/stmts6.go | 8 + .../go/controlflow/ControlFlowGraph/stmts7.go | 28 + .../go/controlflow/ControlFlowGraph/stmts8.go | 14 + .../dataflow/FlowSteps/LocalFlowStep.expected | 133 + .../go/dataflow/FlowSteps/LocalFlowStep.ql | 5 + .../FlowSteps/LocalTaintStep.expected | 57 + .../go/dataflow/FlowSteps/LocalTaintStep.ql | 8 + .../semmle/go/dataflow/FlowSteps/main.go | 35 + .../semmle/go/dataflow/FlowSteps/strings.go | 12 + .../semmle/go/dataflow/FlowSteps/url.go | 54 + .../FunctionInput_getEntryNode.expected | 13 + .../FunctionInput_getEntryNode.ql | 4 + .../FunctionInput_getExitNode.expected | 8 + .../FunctionInput_getExitNode.ql | 4 + .../FunctionOutput_getEntryNode.expected | 19 + .../FunctionOutput_getEntryNode.ql | 4 + .../FunctionOutput_getExitNode.expected | 7 + .../FunctionOutput_getExitNode.ql | 4 + .../dataflow/FunctionInputsAndOutputs/main.go | 58 + .../GlobalValueNumber.expected | 42 + .../GlobalValueNumbering/GlobalValueNumber.ql | 13 + .../UniqueGlobalValueNumber.expected | 0 .../UniqueGlobalValueNumber.ql | 7 + .../go/dataflow/GlobalValueNumbering/main.go | 39 + .../GlobalValueNumbering/regressions.go | 11 + .../InterProceduralDataFlow/Test.expected | 18 + .../dataflow/InterProceduralDataFlow/Test.ql | 23 + .../InterProceduralDataFlow/destructuring.go | 37 + .../InterProceduralDataFlow/dispatch.go | 35 + .../InterProceduralDataFlow/fields.go | 36 + .../dataflow/InterProceduralDataFlow/main.go | 56 + .../InterProceduralDataFlow/pointers.go | 31 + .../Nodes/BinaryOperationNodes.expected | 3 + .../go/dataflow/Nodes/BinaryOperationNodes.ql | 4 + .../go/dataflow/Nodes/CallNode.expected | 4 + .../semmle/go/dataflow/Nodes/CallNode.ql | 4 + .../Nodes/CallNode_getArgument.expected | 5 + .../go/dataflow/Nodes/CallNode_getArgument.ql | 4 + .../semmle/go/dataflow/Nodes/main.go | 16 + .../Properties/Property_checkOn.expected | 40 + .../dataflow/Properties/Property_checkOn.ql | 5 + .../semmle/go/dataflow/Properties/main.go | 17 + .../semmle/go/dataflow/SSA/DefUse.expected | 30 + .../semmle/go/dataflow/SSA/DefUse.ql | 4 + .../go/dataflow/SSA/SsaDefinition.expected | 38 + .../semmle/go/dataflow/SSA/SsaDefinition.ql | 4 + .../semmle/go/dataflow/SSA/VarDefs.expected | 34 + .../semmle/go/dataflow/SSA/VarDefs.ql | 5 + .../semmle/go/dataflow/SSA/VarUses.expected | 30 + .../semmle/go/dataflow/SSA/VarUses.ql | 5 + .../semmle/go/dataflow/SSA/main.go | 98 + .../semmle/go/frameworks/HTTP/Header.expected | 10 + .../semmle/go/frameworks/HTTP/Header.ql | 22 + .../go/frameworks/HTTP/RequestBody.expected | 2 + .../semmle/go/frameworks/HTTP/RequestBody.ql | 4 + .../go/frameworks/HTTP/ResponseBody.expected | 2 + .../semmle/go/frameworks/HTTP/ResponseBody.ql | 4 + .../HTTP/UntrustedFlowSources.expected | 11 + .../frameworks/HTTP/UntrustedFlowSources.ql | 3 + .../semmle/go/frameworks/HTTP/main.go | 58 + .../go/frameworks/SQL/QueryString.expected | 10 + .../semmle/go/frameworks/SQL/QueryString.ql | 4 + .../semmle/go/frameworks/SQL/go.mod | 5 + .../semmle/go/frameworks/SQL/main.go | 26 + .../github.com/Masterminds/squirrel/LICENSE | 23 + .../github.com/Masterminds/squirrel/README.md | 3 + .../github.com/Masterminds/squirrel/go.mod | 1 + .../Masterminds/squirrel/squirrel.go | 23 + .../go/frameworks/SQL/vendor/modules.txt | 2 + .../frameworks/TaintSteps/TaintStep.expected | 16 + .../go/frameworks/TaintSteps/TaintStep.ql | 7 + .../semmle/go/frameworks/TaintSteps/crypto.go | 13 + .../semmle/go/frameworks/TaintSteps/main.go | 23 + .../CleartextPasswordExpr.expected | 12 + .../SensitiveActions/CleartextPasswordExpr.ql | 5 + .../SensitiveActions/DummyPasswords.expected | 9 + .../SensitiveActions/DummyPasswords.ql | 20 + .../SensitiveActions/SensitiveAction.expected | 2 + .../SensitiveActions/SensitiveAction.ql | 4 + .../SensitiveActions/SensitiveExpr.expected | 13 + .../SensitiveActions/SensitiveExpr.ql | 4 + .../go/security/SensitiveActions/main.go | 51 + .../SensitiveActions/sensitiveactions.go | 16 + .../AlertSuppression.expected | 51 + .../AlertSuppression/AlertSuppression.qlref | 1 + .../AlertSuppressionExample.go | 15 + ql/test/query-tests/AlertSuppression/tst.go | 34 + .../AlertSuppression/tstWindows.go | 34 + .../InconsistentLoopOrientation.expected | 4 + .../InconsistentLoopOrientation.go | 13 + .../InconsistentLoopOrientation.qlref | 1 + .../InconsistentLoopOrientationGood.go | 13 + .../InconsistentLoopOrientation/main.go | 42 + .../LengthComparisonOffByOne.expected | 3 + .../LengthComparisonOffByOne.go | 15 + .../LengthComparisonOffByOne.qlref | 1 + .../LengthComparisonOffByOneGood.go | 14 + .../LengthComparisonOffByOne/main.go | 86 + .../MistypedExponentiation.expected | 6 + .../MistypedExponentiation.go | 7 + .../MistypedExponentiation.qlref | 1 + .../MistypedExponentiation/main.go | 27 + .../WhitespaceContradictsPrecedence.expected | 2 + .../WhitespaceContradictsPrecedence.go | 5 + .../WhitespaceContradictsPrecedence.qlref | 1 + .../WhitespaceContradictsPrecedenceGood.go | 5 + .../WhitespaceContradictsPrecedence/main.go | 43 + .../CompareIdenticalValues.expected | 3 + .../CompareIdenticalValues.go | 12 + .../CompareIdenticalValues.qlref | 1 + .../CompareIdenticalValuesGood.go | 8 + .../CompareIdenticalValues/constants.go | 5 + .../CompareIdenticalValues/tst.go | 39 + .../CompareIdenticalValues/vp.go | 17 + .../DeadStoreOfField.expected | 1 + .../DeadStoreOfField/DeadStoreOfField.go | 9 + .../DeadStoreOfField/DeadStoreOfField.qlref | 1 + .../DeadStoreOfField/DeadStoreOfFieldGood.go | 5 + .../RedundantCode/DeadStoreOfField/main.go | 72 + .../DeadStoreOfLocal.expected | 29 + .../DeadStoreOfLocal/DeadStoreOfLocal.qlref | 1 + .../RedundantCode/DeadStoreOfLocal/main.go | 33 + .../DeadStoreOfLocal/testdata.go | 611 + .../DuplicateBranches.expected | 2 + .../DuplicateBranches/DuplicateBranches.go | 9 + .../DuplicateBranches/DuplicateBranches.qlref | 1 + .../DuplicateBranchesGood.go | 9 + .../RedundantCode/DuplicateBranches/main.go | 29 + .../DuplicateCondition.expected | 2 + .../DuplicateCondition/DuplicateCondition.go | 11 + .../DuplicateCondition.qlref | 1 + .../DuplicateConditionGood.go | 11 + .../RedundantCode/DuplicateCondition/funcs.go | 5 + .../RedundantCode/DuplicateCondition/tst.go | 12 + .../DuplicateSwitchCase.expected | 2 + .../DuplicateSwitchCase.go | 12 + .../DuplicateSwitchCase.qlref | 1 + .../DuplicateSwitchCaseGood.go | 12 + .../DuplicateSwitchCase/funcs.go | 5 + .../RedundantCode/DuplicateSwitchCase/tst.go | 25 + .../ExprHasNoEffect/ExprHasNoEffect.expected | 4 + .../ExprHasNoEffect/ExprHasNoEffect.go | 15 + .../ExprHasNoEffect/ExprHasNoEffect.qlref | 1 + .../ExprHasNoEffect/ExprHasNoEffectGood.go | 9 + .../RedundantCode/ExprHasNoEffect/main.go | 37 + .../RedundantCode/ExprHasNoEffect/tst.go | 7 + .../NegativeLengthCheck.expected | 5 + .../NegativeLengthCheck.go | 8 + .../NegativeLengthCheck.qlref | 1 + .../NegativeLengthCheckGood.go | 8 + .../RedundantCode/NegativeLengthCheck/main.go | 25 + .../RedundantExpr/RedundantExpr.expected | 5 + .../RedundantExpr/RedundantExpr.go | 5 + .../RedundantExpr/RedundantExpr.qlref | 1 + .../RedundantExpr/RedundantExprGood.go | 5 + .../RedundantCode/RedundantExpr/tst.go | 28 + .../SelfAssignment/SelfAssignment.expected | 2 + .../SelfAssignment/SelfAssignment.go | 13 + .../SelfAssignment/SelfAssignment.qlref | 1 + .../SelfAssignment/SelfAssignmentGood.go | 5 + .../RedundantCode/SelfAssignment/tst.go | 6 + .../ShiftOutOfRange/ShiftOutOfRange.expected | 4 + .../ShiftOutOfRange/ShiftOutOfRange.go | 7 + .../ShiftOutOfRange/ShiftOutOfRange.qlref | 1 + .../ShiftOutOfRange/ShiftOutOfRangeGood.go | 7 + .../RedundantCode/ShiftOutOfRange/main.go | 28 + .../UnreachableStatement.expected | 8 + .../UnreachableStatement.go | 13 + .../UnreachableStatement.qlref | 1 + .../UnreachableStatementGood.go | 13 + .../UnreachableStatement/main.go | 104 + .../IncompleteHostnameRegexp.expected | 6 + .../IncompleteHostnameRegexp.go | 16 + .../IncompleteHostnameRegexp.qlref | 1 + .../IncompleteHostnameRegexpGood.go | 16 + .../CWE-020/IncompleteHostnameRegexp/main.go | 14 + .../MissingRegexpAnchor.expected | 10 + .../MissingRegexpAnchor.go | 16 + .../MissingRegexpAnchor.qlref | 1 + .../MissingRegexpAnchorGood.go | 16 + .../CWE-020/MissingRegexpAnchor/main.go | 37 + .../Security/CWE-022/TaintedPath.expected | 6 + .../Security/CWE-022/TaintedPath.go | 19 + .../Security/CWE-022/TaintedPath.qlref | 1 + .../Security/CWE-022/ZipSlip.expected | 6 + .../query-tests/Security/CWE-022/ZipSlip.go | 16 + .../Security/CWE-022/ZipSlip.qlref | 1 + .../Security/CWE-022/ZipSlipGood.go | 19 + ql/test/query-tests/Security/CWE-022/tst.go | 43 + .../CWE-078/CommandInjection.expected | 4 + .../Security/CWE-078/CommandInjection.go | 12 + .../Security/CWE-078/CommandInjection.qlref | 1 + .../Security/CWE-079/ReflectedXss.expected | 6 + .../Security/CWE-079/ReflectedXss.go | 20 + .../Security/CWE-079/ReflectedXss.qlref | 1 + .../Security/CWE-079/ReflectedXssGood.go | 21 + .../Security/CWE-079/contenttype.go | 31 + ql/test/query-tests/Security/CWE-079/util.go | 5 + .../Security/CWE-089/SqlInjection.expected | 6 + .../Security/CWE-089/SqlInjection.go | 13 + .../Security/CWE-089/SqlInjection.qlref | 1 + .../Security/CWE-089/SqlInjectionGood.go | 11 + ql/test/query-tests/Security/CWE-089/main.go | 12 + .../CWE-312/CleartextLogging.expected | 54 + .../Security/CWE-312/CleartextLogging.go | 17 + .../Security/CWE-312/CleartextLogging.qlref | 1 + .../Security/CWE-312/CleartextLoggingGood.go | 19 + .../Security/CWE-312/CleartextStorage.go | 21 + .../Security/CWE-312/CleartextStorageGood.go | 62 + ql/test/query-tests/Security/CWE-312/go.mod | 3 + ql/test/query-tests/Security/CWE-312/main.go | 27 + .../query-tests/Security/CWE-312/passwords.go | 128 + .../query-tests/Security/CWE-312/server1.go | 32 + ql/test/query-tests/Security/CWE-312/util.go | 69 + .../Security/CWE-601/OpenUrlRedirect.expected | 16 + .../Security/CWE-601/OpenUrlRedirect.go | 12 + .../Security/CWE-601/OpenUrlRedirect.qlref | 1 + .../Security/CWE-601/OpenUrlRedirectGood.go | 22 + .../query-tests/Security/CWE-601/stdlib.go | 107 + ql/test/query-tests/Security/CWE-601/util.go | 5 + .../CWE-798/AlertSuppressionExample.go | 15 + .../CWE-798/HardcodedCredentials.expected | 7 + .../Security/CWE-798/HardcodedCredentials.go | 20 + .../CWE-798/HardcodedCredentials.qlref | 1 + ql/test/query-tests/Security/CWE-798/main.go | 58 + .../definitions/definitions.expected | 4 + .../query-tests/definitions/definitions.qlref | 1 + ql/test/query-tests/definitions/greet.go | 7 + ql/test/query-tests/definitions/main.go | 13 + .../ClassifyFiles/ClassifyFiles.expected | 1 + .../filters/ClassifyFiles/ClassifyFiles.qlref | 1 + .../filters/ClassifyFiles/hello.go | 9 + templates/project/project | 9 + templates/project/variables | 4 + tools/bootstrap.cmd | 1 + tools/bootstrap.sh | 5 + tools/index.cmd | 1 + tools/index.sh | 5 + tools/qltest.cmd | 1 + tools/qltest.sh | 5 + tools/utils.sh | 24 + upgrades/initial/go.dbscheme | 402 + vendor/golang.org/x/tools/AUTHORS | 3 + vendor/golang.org/x/tools/CONTRIBUTORS | 3 + vendor/golang.org/x/tools/LICENSE | 27 + vendor/golang.org/x/tools/PATENTS | 22 + .../x/tools/go/gcexportdata/gcexportdata.go | 109 + .../x/tools/go/gcexportdata/importer.go | 73 + .../x/tools/go/internal/gcimporter/bexport.go | 852 ++ .../x/tools/go/internal/gcimporter/bimport.go | 1039 ++ .../go/internal/gcimporter/exportdata.go | 93 + .../go/internal/gcimporter/gcimporter.go | 1078 ++ .../x/tools/go/internal/gcimporter/iexport.go | 739 + .../x/tools/go/internal/gcimporter/iimport.go | 630 + .../go/internal/gcimporter/newInterface10.go | 21 + .../go/internal/gcimporter/newInterface11.go | 13 + .../tools/go/internal/packagesdriver/sizes.go | 173 + vendor/golang.org/x/tools/go/packages/doc.go | 222 + .../x/tools/go/packages/external.go | 100 + .../golang.org/x/tools/go/packages/golist.go | 1145 ++ .../x/tools/go/packages/golist_overlay.go | 293 + .../x/tools/go/packages/packages.go | 1113 ++ .../golang.org/x/tools/go/packages/visit.go | 55 + .../x/tools/internal/fastwalk/fastwalk.go | 196 + .../fastwalk/fastwalk_dirent_fileno.go | 13 + .../internal/fastwalk/fastwalk_dirent_ino.go | 14 + .../fastwalk/fastwalk_dirent_namlen_bsd.go | 13 + .../fastwalk/fastwalk_dirent_namlen_linux.go | 29 + .../internal/fastwalk/fastwalk_portable.go | 37 + .../tools/internal/fastwalk/fastwalk_unix.go | 127 + .../x/tools/internal/gopathwalk/walk.go | 270 + .../x/tools/internal/semver/semver.go | 388 + .../golang.org/x/tools/internal/span/parse.go | 100 + .../golang.org/x/tools/internal/span/span.go | 285 + .../golang.org/x/tools/internal/span/token.go | 151 + .../x/tools/internal/span/token111.go | 39 + .../x/tools/internal/span/token112.go | 16 + .../golang.org/x/tools/internal/span/uri.go | 152 + .../golang.org/x/tools/internal/span/utf16.go | 94 + vendor/modules.txt | 9 + 583 files changed, 52850 insertions(+) create mode 100644 .codeqlmanifest.json create mode 100644 .gitignore create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 COPYRIGHT create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 alert_weighting.properties create mode 100644 build/.gitkeep create mode 100644 codeql-extractor.yml create mode 100644 codeql-tools/autobuild.cmd create mode 100755 codeql-tools/autobuild.sh create mode 100644 extractor/cli/go-autobuilder/go-autobuilder.go create mode 100644 extractor/cli/go-autobuilder/go-autobuilder_test.go create mode 100644 extractor/cli/go-bootstrap/go-bootstrap.go create mode 100644 extractor/cli/go-extractor/go-extractor.go create mode 100644 extractor/cli/go-tokenizer/go-tokenizer.go create mode 100644 extractor/dbscheme/dbscheme.go create mode 100644 extractor/dbscheme/tables.go create mode 100644 extractor/extractor.go create mode 100644 extractor/net/sourceforge/pmd/cpd/AbstractLanguage.java create mode 100644 extractor/net/sourceforge/pmd/cpd/GoLanguage.java create mode 100644 extractor/net/sourceforge/pmd/cpd/SourceCode.java create mode 100644 extractor/net/sourceforge/pmd/cpd/TokenEntry.java create mode 100644 extractor/net/sourceforge/pmd/cpd/Tokenizer.java create mode 100644 extractor/opencsv/CSVParser.java create mode 100644 extractor/opencsv/CSVReader.java create mode 100644 extractor/semaphore.go create mode 100644 extractor/srcarchive/projectlayout.go create mode 100644 extractor/srcarchive/projectlayout_test.go create mode 100644 extractor/srcarchive/srcarchive.go create mode 100644 extractor/trap/labels.go create mode 100644 extractor/trap/trapwriter.go create mode 100644 extractor/trap/util.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 ql/config/legacy-support/qlpack.yml create mode 100644 ql/config/suites/lgtm/go-alerts-lgtm create mode 100644 ql/config/suites/lgtm/go-lgtm create mode 100644 ql/config/suites/lgtm/go-metrics-lgtm create mode 100644 ql/config/suites/lgtm/go-util-lgtm create mode 100644 ql/src/.project create mode 100644 ql/src/.qlpath create mode 100644 ql/src/AlertSuppression.ql create mode 100644 ql/src/Customizations.qll create mode 100644 ql/src/InconsistentCode/InconsistentLoopOrientation.go create mode 100644 ql/src/InconsistentCode/InconsistentLoopOrientation.qhelp create mode 100644 ql/src/InconsistentCode/InconsistentLoopOrientation.ql create mode 100644 ql/src/InconsistentCode/InconsistentLoopOrientationGood.go create mode 100644 ql/src/InconsistentCode/LengthComparisonOffByOne.go create mode 100644 ql/src/InconsistentCode/LengthComparisonOffByOne.qhelp create mode 100644 ql/src/InconsistentCode/LengthComparisonOffByOne.ql create mode 100644 ql/src/InconsistentCode/LengthComparisonOffByOneGood.go create mode 100644 ql/src/InconsistentCode/MistypedExponentiation.go create mode 100644 ql/src/InconsistentCode/MistypedExponentiation.qhelp create mode 100644 ql/src/InconsistentCode/MistypedExponentiation.ql create mode 100644 ql/src/InconsistentCode/WhitespaceContradictsPrecedence.go create mode 100644 ql/src/InconsistentCode/WhitespaceContradictsPrecedence.qhelp create mode 100644 ql/src/InconsistentCode/WhitespaceContradictsPrecedence.ql create mode 100644 ql/src/InconsistentCode/WhitespaceContradictsPrecedenceGood.go create mode 100644 ql/src/Metrics/FLinesOfCode.qhelp create mode 100644 ql/src/Metrics/FLinesOfCode.ql create mode 100644 ql/src/Metrics/FLinesOfComment.qhelp create mode 100644 ql/src/Metrics/FLinesOfComment.ql create mode 100644 ql/src/RedundantCode/Clones.qll create mode 100644 ql/src/RedundantCode/CompareIdenticalValues.go create mode 100644 ql/src/RedundantCode/CompareIdenticalValues.qhelp create mode 100644 ql/src/RedundantCode/CompareIdenticalValues.ql create mode 100644 ql/src/RedundantCode/CompareIdenticalValuesGood.go create mode 100644 ql/src/RedundantCode/DeadStoreOfField.go create mode 100644 ql/src/RedundantCode/DeadStoreOfField.qhelp create mode 100644 ql/src/RedundantCode/DeadStoreOfField.ql create mode 100644 ql/src/RedundantCode/DeadStoreOfFieldGood.go create mode 100644 ql/src/RedundantCode/DeadStoreOfLocal.qhelp create mode 100644 ql/src/RedundantCode/DeadStoreOfLocal.ql create mode 100644 ql/src/RedundantCode/DeadStoreOfLocalBad.go create mode 100644 ql/src/RedundantCode/DeadStoreOfLocalGood.go create mode 100644 ql/src/RedundantCode/DuplicateBranches.go create mode 100644 ql/src/RedundantCode/DuplicateBranches.qhelp create mode 100644 ql/src/RedundantCode/DuplicateBranches.ql create mode 100644 ql/src/RedundantCode/DuplicateBranchesGood.go create mode 100644 ql/src/RedundantCode/DuplicateCondition.go create mode 100644 ql/src/RedundantCode/DuplicateCondition.qhelp create mode 100644 ql/src/RedundantCode/DuplicateCondition.ql create mode 100644 ql/src/RedundantCode/DuplicateConditionGood.go create mode 100644 ql/src/RedundantCode/DuplicateSwitchCase.go create mode 100644 ql/src/RedundantCode/DuplicateSwitchCase.qhelp create mode 100644 ql/src/RedundantCode/DuplicateSwitchCase.ql create mode 100644 ql/src/RedundantCode/DuplicateSwitchCaseGood.go create mode 100644 ql/src/RedundantCode/ExprHasNoEffect.go create mode 100644 ql/src/RedundantCode/ExprHasNoEffect.qhelp create mode 100644 ql/src/RedundantCode/ExprHasNoEffect.ql create mode 100644 ql/src/RedundantCode/ExprHasNoEffectGood.go create mode 100644 ql/src/RedundantCode/NegativeLengthCheck.go create mode 100644 ql/src/RedundantCode/NegativeLengthCheck.qhelp create mode 100644 ql/src/RedundantCode/NegativeLengthCheck.ql create mode 100644 ql/src/RedundantCode/NegativeLengthCheckGood.go create mode 100644 ql/src/RedundantCode/RedundantExpr.go create mode 100644 ql/src/RedundantCode/RedundantExpr.qhelp create mode 100644 ql/src/RedundantCode/RedundantExpr.ql create mode 100644 ql/src/RedundantCode/RedundantExprGood.go create mode 100644 ql/src/RedundantCode/SelfAssignment.go create mode 100644 ql/src/RedundantCode/SelfAssignment.qhelp create mode 100644 ql/src/RedundantCode/SelfAssignment.ql create mode 100644 ql/src/RedundantCode/SelfAssignmentGood.go create mode 100644 ql/src/RedundantCode/ShiftOutOfRange.go create mode 100644 ql/src/RedundantCode/ShiftOutOfRange.qhelp create mode 100644 ql/src/RedundantCode/ShiftOutOfRange.ql create mode 100644 ql/src/RedundantCode/ShiftOutOfRangeGood.go create mode 100644 ql/src/RedundantCode/UnreachableStatement.go create mode 100644 ql/src/RedundantCode/UnreachableStatement.qhelp create mode 100644 ql/src/RedundantCode/UnreachableStatement.ql create mode 100644 ql/src/RedundantCode/UnreachableStatementGood.go create mode 100644 ql/src/Security/CWE-020/IncompleteHostnameRegexp.go create mode 100644 ql/src/Security/CWE-020/IncompleteHostnameRegexp.qhelp create mode 100644 ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql create mode 100644 ql/src/Security/CWE-020/IncompleteHostnameRegexpGood.go create mode 100644 ql/src/Security/CWE-020/MissingRegexpAnchor.go create mode 100644 ql/src/Security/CWE-020/MissingRegexpAnchor.qhelp create mode 100644 ql/src/Security/CWE-020/MissingRegexpAnchor.ql create mode 100644 ql/src/Security/CWE-020/MissingRegexpAnchorGood.go create mode 100644 ql/src/Security/CWE-022/TaintedPath.go create mode 100644 ql/src/Security/CWE-022/TaintedPath.qhelp create mode 100644 ql/src/Security/CWE-022/TaintedPath.ql create mode 100644 ql/src/Security/CWE-022/ZipSlip.go create mode 100644 ql/src/Security/CWE-022/ZipSlip.qhelp create mode 100644 ql/src/Security/CWE-022/ZipSlip.ql create mode 100644 ql/src/Security/CWE-022/ZipSlipGood.go create mode 100644 ql/src/Security/CWE-078/CommandInjection.go create mode 100644 ql/src/Security/CWE-078/CommandInjection.qhelp create mode 100644 ql/src/Security/CWE-078/CommandInjection.ql create mode 100644 ql/src/Security/CWE-079/ReflectedXss.go create mode 100644 ql/src/Security/CWE-079/ReflectedXss.qhelp create mode 100644 ql/src/Security/CWE-079/ReflectedXss.ql create mode 100644 ql/src/Security/CWE-079/ReflectedXssGood.go create mode 100644 ql/src/Security/CWE-089/SqlInjection.go create mode 100644 ql/src/Security/CWE-089/SqlInjection.qhelp create mode 100644 ql/src/Security/CWE-089/SqlInjection.ql create mode 100644 ql/src/Security/CWE-089/SqlInjectionGood.go create mode 100644 ql/src/Security/CWE-312/CleartextLogging.go create mode 100644 ql/src/Security/CWE-312/CleartextLogging.qhelp create mode 100644 ql/src/Security/CWE-312/CleartextLogging.ql create mode 100644 ql/src/Security/CWE-312/CleartextLoggingGood.go create mode 100644 ql/src/Security/CWE-601/OpenUrlRedirect.go create mode 100644 ql/src/Security/CWE-601/OpenUrlRedirect.qhelp create mode 100644 ql/src/Security/CWE-601/OpenUrlRedirect.ql create mode 100644 ql/src/Security/CWE-601/OpenUrlRedirectGood.go create mode 100644 ql/src/Security/CWE-798/HardcodedCredentials.go create mode 100644 ql/src/Security/CWE-798/HardcodedCredentials.qhelp create mode 100644 ql/src/Security/CWE-798/HardcodedCredentials.ql create mode 100644 ql/src/codeql-suites/go-lgtm-full.qls create mode 100644 ql/src/codeql-suites/go-lgtm.qls create mode 100644 ql/src/definitions.ql create mode 100644 ql/src/filters/ClassifyFiles.ql create mode 100644 ql/src/go.dbscheme create mode 100644 ql/src/go.dbscheme.stats create mode 100644 ql/src/go.qll create mode 100644 ql/src/qlpack.yml create mode 100644 ql/src/queries.xml create mode 100644 ql/src/semmle/go/AST.qll create mode 100644 ql/src/semmle/go/Comments.qll create mode 100644 ql/src/semmle/go/Concepts.qll create mode 100644 ql/src/semmle/go/Decls.qll create mode 100644 ql/src/semmle/go/Expr.qll create mode 100644 ql/src/semmle/go/Files.qll create mode 100644 ql/src/semmle/go/Locations.qll create mode 100644 ql/src/semmle/go/Packages.qll create mode 100644 ql/src/semmle/go/Scopes.qll create mode 100644 ql/src/semmle/go/Stmt.qll create mode 100644 ql/src/semmle/go/StringConcatenation.qll create mode 100644 ql/src/semmle/go/Types.qll create mode 100644 ql/src/semmle/go/Util.qll create mode 100644 ql/src/semmle/go/controlflow/BasicBlocks.qll create mode 100644 ql/src/semmle/go/controlflow/ControlFlowGraph.qll create mode 100644 ql/src/semmle/go/controlflow/ControlFlowGraphImpl.qll create mode 100644 ql/src/semmle/go/controlflow/IR.qll create mode 100644 ql/src/semmle/go/dataflow/DataFlow.qll create mode 100644 ql/src/semmle/go/dataflow/FunctionInputsAndOutputs.qll create mode 100644 ql/src/semmle/go/dataflow/GlobalValueNumbering.qll create mode 100644 ql/src/semmle/go/dataflow/Properties.qll create mode 100644 ql/src/semmle/go/dataflow/SSA.qll create mode 100644 ql/src/semmle/go/dataflow/SsaImpl.qll create mode 100644 ql/src/semmle/go/dataflow/TaintTracking.qll create mode 100644 ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll create mode 100644 ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll create mode 100644 ql/src/semmle/go/dataflow/internal/DataFlowImplCommon.qll create mode 100644 ql/src/semmle/go/dataflow/internal/DataFlowImplSpecific.qll create mode 100644 ql/src/semmle/go/dataflow/internal/DataFlowPrivate.qll create mode 100644 ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll create mode 100644 ql/src/semmle/go/frameworks/Glog.qll create mode 100644 ql/src/semmle/go/frameworks/HTTP.qll create mode 100644 ql/src/semmle/go/frameworks/Logrus.qll create mode 100644 ql/src/semmle/go/frameworks/SQL.qll create mode 100644 ql/src/semmle/go/frameworks/Stdlib.qll create mode 100644 ql/src/semmle/go/frameworks/SystemCommandExecutors.qll create mode 100644 ql/src/semmle/go/security/CleartextLogging.qll create mode 100644 ql/src/semmle/go/security/CleartextLoggingCustomizations.qll create mode 100644 ql/src/semmle/go/security/CommandInjection.qll create mode 100644 ql/src/semmle/go/security/CommandInjectionCustomizations.qll create mode 100644 ql/src/semmle/go/security/FlowSources.qll create mode 100644 ql/src/semmle/go/security/OpenUrlRedirect.qll create mode 100644 ql/src/semmle/go/security/OpenUrlRedirectCustomizations.qll create mode 100644 ql/src/semmle/go/security/ReflectedXss.qll create mode 100644 ql/src/semmle/go/security/ReflectedXssCustomizations.qll create mode 100644 ql/src/semmle/go/security/SensitiveActions.qll create mode 100644 ql/src/semmle/go/security/SqlInjection.qll create mode 100644 ql/src/semmle/go/security/SqlInjectionCustomizations.qll create mode 100644 ql/src/semmle/go/security/TaintedPath.qll create mode 100644 ql/src/semmle/go/security/TaintedPathCustomizations.qll create mode 100644 ql/src/semmle/go/security/UrlConcatenation.qll create mode 100644 ql/src/semmle/go/security/ZipSlip.qll create mode 100644 ql/src/semmle/go/security/ZipSlipCustomizations.qll create mode 100644 ql/test/.project create mode 100644 ql/test/.qlpath create mode 100644 ql/test/extractor-tests/go1.13/literals.expected create mode 100644 ql/test/extractor-tests/go1.13/literals.ql create mode 100644 ql/test/extractor-tests/go1.13/tst.go create mode 100644 ql/test/extractor-tests/robustness/tst.expected create mode 100644 ql/test/extractor-tests/robustness/tst.go create mode 100644 ql/test/extractor-tests/robustness/tst.ql create mode 100644 ql/test/library-tests/semmle/go/Expr/BasicLit_getText.expected create mode 100644 ql/test/library-tests/semmle/go/Expr/BasicLit_getText.ql create mode 100644 ql/test/library-tests/semmle/go/Expr/BasicLit_getValue.expected create mode 100644 ql/test/library-tests/semmle/go/Expr/BasicLit_getValue.ql create mode 100644 ql/test/library-tests/semmle/go/Expr/CompositeLit.expected create mode 100644 ql/test/library-tests/semmle/go/Expr/CompositeLit.ql create mode 100644 ql/test/library-tests/semmle/go/Expr/ConstantValues.expected create mode 100644 ql/test/library-tests/semmle/go/Expr/ConstantValues.ql create mode 100644 ql/test/library-tests/semmle/go/Expr/Ident.expected create mode 100644 ql/test/library-tests/semmle/go/Expr/Ident.ql create mode 100644 ql/test/library-tests/semmle/go/Expr/consts.go create mode 100644 ql/test/library-tests/semmle/go/Expr/literals.go create mode 100644 ql/test/library-tests/semmle/go/Scopes/DeclaredEntity.expected create mode 100644 ql/test/library-tests/semmle/go/Scopes/DeclaredEntity.ql create mode 100644 ql/test/library-tests/semmle/go/Scopes/EntityType.expected create mode 100644 ql/test/library-tests/semmle/go/Scopes/EntityType.ql create mode 100644 ql/test/library-tests/semmle/go/Scopes/EntityUse.expected create mode 100644 ql/test/library-tests/semmle/go/Scopes/EntityUse.ql create mode 100644 ql/test/library-tests/semmle/go/Scopes/MethodImplements.expected create mode 100644 ql/test/library-tests/semmle/go/Scopes/MethodImplements.ql create mode 100644 ql/test/library-tests/semmle/go/Scopes/MethodImplementsName.expected create mode 100644 ql/test/library-tests/semmle/go/Scopes/MethodImplementsName.ql create mode 100644 ql/test/library-tests/semmle/go/Scopes/Methods.expected create mode 100644 ql/test/library-tests/semmle/go/Scopes/Methods.ql create mode 100644 ql/test/library-tests/semmle/go/Scopes/TypeImplements.expected create mode 100644 ql/test/library-tests/semmle/go/Scopes/TypeImplements.ql create mode 100644 ql/test/library-tests/semmle/go/Scopes/main.go create mode 100644 ql/test/library-tests/semmle/go/Scopes/types.go create mode 100644 ql/test/library-tests/semmle/go/Types/MethodDecls.expected create mode 100644 ql/test/library-tests/semmle/go/Types/MethodDecls.ql create mode 100644 ql/test/library-tests/semmle/go/Types/Methods.expected create mode 100644 ql/test/library-tests/semmle/go/Types/Methods.ql create mode 100644 ql/test/library-tests/semmle/go/Types/QualifiedNames.expected create mode 100644 ql/test/library-tests/semmle/go/Types/QualifiedNames.ql create mode 100644 ql/test/library-tests/semmle/go/Types/SignatureType_getNumParameter.expected create mode 100644 ql/test/library-tests/semmle/go/Types/SignatureType_getNumParameter.ql create mode 100644 ql/test/library-tests/semmle/go/Types/SignatureType_getNumResult.expected create mode 100644 ql/test/library-tests/semmle/go/Types/SignatureType_getNumResult.ql create mode 100644 ql/test/library-tests/semmle/go/Types/StructFields.expected create mode 100644 ql/test/library-tests/semmle/go/Types/StructFields.ql create mode 100644 ql/test/library-tests/semmle/go/Types/Types.expected create mode 100644 ql/test/library-tests/semmle/go/Types/Types.ql create mode 100644 ql/test/library-tests/semmle/go/Types/main.go create mode 100644 ql/test/library-tests/semmle/go/Types/pkg1/tst.go create mode 100644 ql/test/library-tests/semmle/go/Types/pkg2/tst.go create mode 100644 ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.expected create mode 100644 ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.go create mode 100644 ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.ql create mode 100644 ql/test/library-tests/semmle/go/concepts/Regexp/RegexpMatchFunction.expected create mode 100644 ql/test/library-tests/semmle/go/concepts/Regexp/RegexpMatchFunction.ql create mode 100644 ql/test/library-tests/semmle/go/concepts/Regexp/RegexpPattern.expected create mode 100644 ql/test/library-tests/semmle/go/concepts/Regexp/RegexpPattern.ql create mode 100644 ql/test/library-tests/semmle/go/concepts/Regexp/RegexpReplaceFunction.expected create mode 100644 ql/test/library-tests/semmle/go/concepts/Regexp/RegexpReplaceFunction.ql create mode 100644 ql/test/library-tests/semmle/go/concepts/Regexp/stdlib.go create mode 100644 ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/SystemCommandExecution.expected create mode 100644 ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/SystemCommandExecution.ql create mode 100644 ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/go.mod create mode 100644 ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/gosh.go create mode 100644 ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/main.go create mode 100644 ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/LICENSE create mode 100644 ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/README.md create mode 100644 ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/sh.go create mode 100644 ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/modules.txt create mode 100644 ql/test/library-tests/semmle/go/concepts/Templates/Templates.expected create mode 100644 ql/test/library-tests/semmle/go/concepts/Templates/Templates.ql create mode 100644 ql/test/library-tests/semmle/go/concepts/Templates/main.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CFG.expected create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CFG.ql create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.ql create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/exprs.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/hello.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/linux.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/main.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/nonlinux.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts2.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts3.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts4.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts5.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts6.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts7.go create mode 100644 ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts8.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalTaintStep.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalTaintStep.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/FlowSteps/main.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/FlowSteps/strings.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/FlowSteps/url.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getEntryNode.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getEntryNode.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getEntryNode.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getEntryNode.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getExitNode.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getExitNode.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/main.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/GlobalValueNumber.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/GlobalValueNumber.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/UniqueGlobalValueNumber.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/UniqueGlobalValueNumber.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/main.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/regressions.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/destructuring.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/dispatch.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/fields.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/main.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/pointers.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode_getArgument.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode_getArgument.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/Nodes/main.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/Properties/Property_checkOn.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/Properties/Property_checkOn.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/Properties/main.go create mode 100644 ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/SSA/main.go create mode 100644 ql/test/library-tests/semmle/go/frameworks/HTTP/Header.expected create mode 100644 ql/test/library-tests/semmle/go/frameworks/HTTP/Header.ql create mode 100644 ql/test/library-tests/semmle/go/frameworks/HTTP/RequestBody.expected create mode 100644 ql/test/library-tests/semmle/go/frameworks/HTTP/RequestBody.ql create mode 100644 ql/test/library-tests/semmle/go/frameworks/HTTP/ResponseBody.expected create mode 100644 ql/test/library-tests/semmle/go/frameworks/HTTP/ResponseBody.ql create mode 100644 ql/test/library-tests/semmle/go/frameworks/HTTP/UntrustedFlowSources.expected create mode 100644 ql/test/library-tests/semmle/go/frameworks/HTTP/UntrustedFlowSources.ql create mode 100644 ql/test/library-tests/semmle/go/frameworks/HTTP/main.go create mode 100644 ql/test/library-tests/semmle/go/frameworks/SQL/QueryString.expected create mode 100644 ql/test/library-tests/semmle/go/frameworks/SQL/QueryString.ql create mode 100644 ql/test/library-tests/semmle/go/frameworks/SQL/go.mod create mode 100644 ql/test/library-tests/semmle/go/frameworks/SQL/main.go create mode 100644 ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/LICENSE create mode 100644 ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/README.md create mode 100644 ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/go.mod create mode 100644 ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/squirrel.go create mode 100644 ql/test/library-tests/semmle/go/frameworks/SQL/vendor/modules.txt create mode 100644 ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.expected create mode 100644 ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.ql create mode 100644 ql/test/library-tests/semmle/go/frameworks/TaintSteps/crypto.go create mode 100644 ql/test/library-tests/semmle/go/frameworks/TaintSteps/main.go create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/CleartextPasswordExpr.expected create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/CleartextPasswordExpr.ql create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/DummyPasswords.expected create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/DummyPasswords.ql create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveAction.expected create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveAction.ql create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveExpr.expected create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveExpr.ql create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/main.go create mode 100644 ql/test/library-tests/semmle/go/security/SensitiveActions/sensitiveactions.go create mode 100644 ql/test/query-tests/AlertSuppression/AlertSuppression.expected create mode 100644 ql/test/query-tests/AlertSuppression/AlertSuppression.qlref create mode 100644 ql/test/query-tests/AlertSuppression/AlertSuppressionExample.go create mode 100644 ql/test/query-tests/AlertSuppression/tst.go create mode 100644 ql/test/query-tests/AlertSuppression/tstWindows.go create mode 100644 ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.expected create mode 100644 ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.go create mode 100644 ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.qlref create mode 100644 ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientationGood.go create mode 100644 ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/main.go create mode 100644 ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.expected create mode 100644 ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.go create mode 100644 ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.qlref create mode 100644 ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOneGood.go create mode 100644 ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/main.go create mode 100644 ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.expected create mode 100644 ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.go create mode 100644 ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.qlref create mode 100644 ql/test/query-tests/InconsistentCode/MistypedExponentiation/main.go create mode 100644 ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.expected create mode 100644 ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.go create mode 100644 ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.qlref create mode 100644 ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedenceGood.go create mode 100644 ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/main.go create mode 100644 ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.expected create mode 100644 ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.go create mode 100644 ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.qlref create mode 100644 ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValuesGood.go create mode 100644 ql/test/query-tests/RedundantCode/CompareIdenticalValues/constants.go create mode 100644 ql/test/query-tests/RedundantCode/CompareIdenticalValues/tst.go create mode 100644 ql/test/query-tests/RedundantCode/CompareIdenticalValues/vp.go create mode 100644 ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.expected create mode 100644 ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.go create mode 100644 ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.qlref create mode 100644 ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfFieldGood.go create mode 100644 ql/test/query-tests/RedundantCode/DeadStoreOfField/main.go create mode 100644 ql/test/query-tests/RedundantCode/DeadStoreOfLocal/DeadStoreOfLocal.expected create mode 100644 ql/test/query-tests/RedundantCode/DeadStoreOfLocal/DeadStoreOfLocal.qlref create mode 100644 ql/test/query-tests/RedundantCode/DeadStoreOfLocal/main.go create mode 100644 ql/test/query-tests/RedundantCode/DeadStoreOfLocal/testdata.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.expected create mode 100644 ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.qlref create mode 100644 ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranchesGood.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateBranches/main.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.expected create mode 100644 ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.qlref create mode 100644 ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateConditionGood.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateCondition/funcs.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateCondition/tst.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.expected create mode 100644 ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.qlref create mode 100644 ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCaseGood.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateSwitchCase/funcs.go create mode 100644 ql/test/query-tests/RedundantCode/DuplicateSwitchCase/tst.go create mode 100644 ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.expected create mode 100644 ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.go create mode 100644 ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.qlref create mode 100644 ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffectGood.go create mode 100644 ql/test/query-tests/RedundantCode/ExprHasNoEffect/main.go create mode 100644 ql/test/query-tests/RedundantCode/ExprHasNoEffect/tst.go create mode 100644 ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.expected create mode 100644 ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.go create mode 100644 ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.qlref create mode 100644 ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheckGood.go create mode 100644 ql/test/query-tests/RedundantCode/NegativeLengthCheck/main.go create mode 100644 ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.expected create mode 100644 ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.go create mode 100644 ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.qlref create mode 100644 ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExprGood.go create mode 100644 ql/test/query-tests/RedundantCode/RedundantExpr/tst.go create mode 100644 ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.expected create mode 100644 ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.go create mode 100644 ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.qlref create mode 100644 ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignmentGood.go create mode 100644 ql/test/query-tests/RedundantCode/SelfAssignment/tst.go create mode 100644 ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.expected create mode 100644 ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.go create mode 100644 ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.qlref create mode 100644 ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRangeGood.go create mode 100644 ql/test/query-tests/RedundantCode/ShiftOutOfRange/main.go create mode 100644 ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected create mode 100644 ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.go create mode 100644 ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.qlref create mode 100644 ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatementGood.go create mode 100644 ql/test/query-tests/RedundantCode/UnreachableStatement/main.go create mode 100644 ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.expected create mode 100644 ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.go create mode 100644 ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.qlref create mode 100644 ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexpGood.go create mode 100644 ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/main.go create mode 100644 ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.expected create mode 100644 ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.go create mode 100644 ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.qlref create mode 100644 ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchorGood.go create mode 100644 ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/main.go create mode 100644 ql/test/query-tests/Security/CWE-022/TaintedPath.expected create mode 100644 ql/test/query-tests/Security/CWE-022/TaintedPath.go create mode 100644 ql/test/query-tests/Security/CWE-022/TaintedPath.qlref create mode 100644 ql/test/query-tests/Security/CWE-022/ZipSlip.expected create mode 100644 ql/test/query-tests/Security/CWE-022/ZipSlip.go create mode 100644 ql/test/query-tests/Security/CWE-022/ZipSlip.qlref create mode 100644 ql/test/query-tests/Security/CWE-022/ZipSlipGood.go create mode 100644 ql/test/query-tests/Security/CWE-022/tst.go create mode 100644 ql/test/query-tests/Security/CWE-078/CommandInjection.expected create mode 100644 ql/test/query-tests/Security/CWE-078/CommandInjection.go create mode 100644 ql/test/query-tests/Security/CWE-078/CommandInjection.qlref create mode 100644 ql/test/query-tests/Security/CWE-079/ReflectedXss.expected create mode 100644 ql/test/query-tests/Security/CWE-079/ReflectedXss.go create mode 100644 ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref create mode 100644 ql/test/query-tests/Security/CWE-079/ReflectedXssGood.go create mode 100644 ql/test/query-tests/Security/CWE-079/contenttype.go create mode 100644 ql/test/query-tests/Security/CWE-079/util.go create mode 100644 ql/test/query-tests/Security/CWE-089/SqlInjection.expected create mode 100644 ql/test/query-tests/Security/CWE-089/SqlInjection.go create mode 100644 ql/test/query-tests/Security/CWE-089/SqlInjection.qlref create mode 100644 ql/test/query-tests/Security/CWE-089/SqlInjectionGood.go create mode 100644 ql/test/query-tests/Security/CWE-089/main.go create mode 100644 ql/test/query-tests/Security/CWE-312/CleartextLogging.expected create mode 100644 ql/test/query-tests/Security/CWE-312/CleartextLogging.go create mode 100644 ql/test/query-tests/Security/CWE-312/CleartextLogging.qlref create mode 100644 ql/test/query-tests/Security/CWE-312/CleartextLoggingGood.go create mode 100644 ql/test/query-tests/Security/CWE-312/CleartextStorage.go create mode 100644 ql/test/query-tests/Security/CWE-312/CleartextStorageGood.go create mode 100644 ql/test/query-tests/Security/CWE-312/go.mod create mode 100644 ql/test/query-tests/Security/CWE-312/main.go create mode 100644 ql/test/query-tests/Security/CWE-312/passwords.go create mode 100644 ql/test/query-tests/Security/CWE-312/server1.go create mode 100644 ql/test/query-tests/Security/CWE-312/util.go create mode 100644 ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.expected create mode 100644 ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.go create mode 100644 ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.qlref create mode 100644 ql/test/query-tests/Security/CWE-601/OpenUrlRedirectGood.go create mode 100644 ql/test/query-tests/Security/CWE-601/stdlib.go create mode 100644 ql/test/query-tests/Security/CWE-601/util.go create mode 100644 ql/test/query-tests/Security/CWE-798/AlertSuppressionExample.go create mode 100644 ql/test/query-tests/Security/CWE-798/HardcodedCredentials.expected create mode 100644 ql/test/query-tests/Security/CWE-798/HardcodedCredentials.go create mode 100644 ql/test/query-tests/Security/CWE-798/HardcodedCredentials.qlref create mode 100644 ql/test/query-tests/Security/CWE-798/main.go create mode 100644 ql/test/query-tests/definitions/definitions.expected create mode 100644 ql/test/query-tests/definitions/definitions.qlref create mode 100644 ql/test/query-tests/definitions/greet.go create mode 100644 ql/test/query-tests/definitions/main.go create mode 100644 ql/test/query-tests/filters/ClassifyFiles/ClassifyFiles.expected create mode 100644 ql/test/query-tests/filters/ClassifyFiles/ClassifyFiles.qlref create mode 100644 ql/test/query-tests/filters/ClassifyFiles/hello.go create mode 100644 templates/project/project create mode 100644 templates/project/variables create mode 100644 tools/bootstrap.cmd create mode 100755 tools/bootstrap.sh create mode 100644 tools/index.cmd create mode 100755 tools/index.sh create mode 100644 tools/qltest.cmd create mode 100755 tools/qltest.sh create mode 100644 tools/utils.sh create mode 100644 upgrades/initial/go.dbscheme create mode 100644 vendor/golang.org/x/tools/AUTHORS create mode 100644 vendor/golang.org/x/tools/CONTRIBUTORS create mode 100644 vendor/golang.org/x/tools/LICENSE create mode 100644 vendor/golang.org/x/tools/PATENTS create mode 100644 vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go create mode 100644 vendor/golang.org/x/tools/go/gcexportdata/importer.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/bexport.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/exportdata.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go create mode 100644 vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go create mode 100644 vendor/golang.org/x/tools/go/packages/doc.go create mode 100644 vendor/golang.org/x/tools/go/packages/external.go create mode 100644 vendor/golang.org/x/tools/go/packages/golist.go create mode 100644 vendor/golang.org/x/tools/go/packages/golist_overlay.go create mode 100644 vendor/golang.org/x/tools/go/packages/packages.go create mode 100644 vendor/golang.org/x/tools/go/packages/visit.go create mode 100644 vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go create mode 100644 vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go create mode 100644 vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go create mode 100644 vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go create mode 100644 vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go create mode 100644 vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go create mode 100644 vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go create mode 100644 vendor/golang.org/x/tools/internal/gopathwalk/walk.go create mode 100644 vendor/golang.org/x/tools/internal/semver/semver.go create mode 100644 vendor/golang.org/x/tools/internal/span/parse.go create mode 100644 vendor/golang.org/x/tools/internal/span/span.go create mode 100644 vendor/golang.org/x/tools/internal/span/token.go create mode 100644 vendor/golang.org/x/tools/internal/span/token111.go create mode 100644 vendor/golang.org/x/tools/internal/span/token112.go create mode 100644 vendor/golang.org/x/tools/internal/span/uri.go create mode 100644 vendor/golang.org/x/tools/internal/span/utf16.go create mode 100644 vendor/modules.txt diff --git a/.codeqlmanifest.json b/.codeqlmanifest.json new file mode 100644 index 00000000..841502e9 --- /dev/null +++ b/.codeqlmanifest.json @@ -0,0 +1,3 @@ +{ "provide": [ "ql/src/qlpack.yml", + "ql/config/legacy-support/qlpack.yml" ], + "ignore": [ "the-extractor-which-needs-to-be-built" ] } diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..bba12d84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# editor and OS artifacts +*~ +.DS_STORE + +# query compilation caches +.cache + +# build artifacts +build/* + +# qltest projects and artifacts +ql/test/**/*.testproj +ql/test/**/*.actual +ql/test/**/go.sum + +# Java class files +**/*.class + +# binaries +tools/bin +tools/tokenizer.jar diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..3a64696b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at opensource@github.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..36f34c76 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,44 @@ +## Contributing + +Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. + +Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). + +Please note that this project is released with a [Contributor Code of Conduct][CODE_OF_CONDUCT.md]. By participating in this project you agree to abide by its terms. + +## Adding a new query + +If you have an idea for a query that you would like to share with other CodeQL users, please open a pull request to add it to this repository. +Follow the steps below to help other users understand what your query does, and to ensure that your query is consistent with the other CodeQL queries. + +1. **Consult the documentation for query writers** + + There is lots of useful documentation to help you write CodeQL queries, ranging from information about query file structure to language-specific tutorials. For more information on the documentation available, see [Writing QL queries](https://help.semmle.com/QL/learn-ql/writing-queries/writing-queries.html) on [help.semmle.com](https://help.semmle.com). + +2. **Format your code correctly** + + All of the standard CodeQL queries and libraries are uniformly formatted for clarity and consistency, so we strongly recommend that all contributions follow the same formatting guidelines. If you use the CodeQL extension for Visual Studio Code, you can auto-format your query in the [QL editor](https://help.semmle.com/ql-for-eclipse/Content/WebHelp/ql-editor.html). For more information, see the [QL style guide](https://github.com/Semmle/ql/blob/master/docs/ql-style-guide.md). + +3. **Make sure your query has the correct metadata** + + Query metadata is used by Semmle's analysis to identify your query and make sure the query results are displayed properly. + The most important metadata to include are the `@name`, `@description`, and the `@kind`. + Other metadata properties (`@precision`, `@severity`, and `@tags`) are usually added after the query has been reviewed by the maintainers. + For more information on writing query metadata, see the [Query metadata style guide](https://github.com/Semmle/ql/blob/master/docs/query-metadata-style-guide.md). + +4. **Make sure the `select` statement is compatible with the query type** + + The `select` statement of your query must be compatible with the query type (determined by the `@kind` metadata property) for alert or path results to be displayed correctly in LGTM and Visual Studio Code. + For more information on `select` statement format, see [Introduction to query files](https://help.semmle.com/QL/learn-ql/writing-queries/introduction-to-queries.html#select-clause) on help.semmle.com. + +5. **Write a query help file** + + Query help files explain the purpose of your query to other users. Write your query help in a `.qhelp` file and save it in the same directory as your new query. + For more information on writing query help, see the [Query help style guide](https://github.com/Semmle/ql/blob/master/docs/query-help-style-guide.md). + +## Resources + +- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) +- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) +- [GitHub Help](https://help.github.com) +- [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 00000000..65d947ff --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,13 @@ +Copyright (c) Semmle Inc and other contributors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..a4cf061b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 GitHub + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..ad2dcec7 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +all: tools ql/src/go.dbscheme + +ifeq ($(OS),Windows_NT) +EXE = .exe +else +EXE = +endif + +.PHONY: tools +tools: tools/bin/go-extractor$(EXE) tools/bin/go-tokenizer$(EXE) tools/bin/go-autobuilder$(EXE) tools/tokenizer.jar tools/bin/go-bootstrap$(EXE) + +tools/bin/go-extractor$(EXE): FORCE + go build -mod=vendor -o $@ ./extractor/cli/go-extractor + +tools/bin/go-tokenizer$(EXE): FORCE + go build -mod=vendor -o $@ ./extractor/cli/go-tokenizer + +tools/bin/go-autobuilder$(EXE): FORCE + go build -mod=vendor -o $@ ./extractor/cli/go-autobuilder + +tools/bin/go-bootstrap$(EXE): FORCE + go build -mod=vendor -o $@ ./extractor/cli/go-bootstrap + +FORCE: + +tools/tokenizer.jar: tools/net/sourceforge/pmd/cpd/GoLanguage.class + jar cf $@ -C tools net + jar uf $@ -C tools opencsv + +tools/net/sourceforge/pmd/cpd/GoLanguage.class: extractor/net/sourceforge/pmd/cpd/GoLanguage.java + javac -cp extractor -d tools $^ + rm tools/net/sourceforge/pmd/cpd/AbstractLanguage.class + rm tools/net/sourceforge/pmd/cpd/SourceCode.class + rm tools/net/sourceforge/pmd/cpd/TokenEntry.class + rm tools/net/sourceforge/pmd/cpd/Tokenizer.class + +ql/src/go.dbscheme: tools/bin/go-extractor$(EXE) + env TRAP_FOLDER=/tmp tools/bin/go-extractor --dbscheme $@ + +ql/src/go.dbscheme.stats: ql/src/go.dbscheme + odasa createProject --force --template templates/project --threads 4 \ + --variable repository https://github.com/golang/tools \ + --variable revision 6e04913c \ + --variable SEMMLE_REPO_URL golang.org/x/tools \ + build/stats-project + odasa addSnapshot --latest --overwrite --name revision --project build/stats-project + odasa buildSnapshot --latest --project build/stats-project + odasa collectStats --dbscheme $^ --db build/stats-project/revision/working/db-go --outputFile $@ + +test: all build/testdb/check-upgrade-path + odasa qltest --language go --library ql/src ql/test + cd extractor; go test -mod=vendor ./... | grep -vF "[no test files]" + +.PHONY: build/testdb/check-upgrade-path +build/testdb/check-upgrade-path : build/testdb/go.dbscheme ql/src/go.dbscheme + odasa upgradeDatabase --db build/testdb --upgrade-packs upgrades + diff -q build/testdb/go.dbscheme ql/src/go.dbscheme + +build/testdb/go.dbscheme: upgrades/initial/go.dbscheme + echo >build/empty.trap + odasa cli --dbscheme upgrades/initial/go.dbscheme --import build/empty.trap --db build/testdb diff --git a/README.md b/README.md new file mode 100644 index 00000000..abcf8fbf --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Go analysis support for CodeQL + +This open-source repository contains the extractor, CodeQL libraries, and queries that power Go +support in [LGTM](https://lgtm.com), CodeQL, and other Semmle products. + +It contains two major components: + - an extractor, itself written in Go, that parses Go source code and converts it into a database + that can be queried using CodeQL. + - static analysis libraries and queries written in [QL](https://help.semmle.com/QL) that can be + used to analyze such a database to find coding mistakes or security vulnerabilities. + +The goal of this project is to provide comprehensive static analysis support for Go in CodeQL. + +## Installation + +Simply clone this repository. There are no external dependencies. + +If you want to use the CodeQL extension for Visual Studio Code, import this repository into your VS +Code workspace. + +## Usage + +To analyze a Go codebase, either use the CodeQL command-line interface to create a database +yourself, or download a pre-built database from LGTM.com. You can then run any of the queries +contained in this repository either on the command line or using the VS Code extension. + +Note that the [lgtm.com](https://github.com/github/codeql-go/tree/lgtm.com) branch of this +repository corresponds to the version of the queries that is currently deployed on LGTM.com. +The [master](https://github.com/github/codeql-go/tree/master) branch may contain changes that +have not been deployed yet, so you may need to upgrade databases downloaded from LGTM.com before +running queries on them. + +## Contributions + +Contributions are welcome! Please see our [contribution guidelines](CONTRIBUTING.md) and our +[code of conduct](CODE_OF_CONDUCT.md) for details on how to participate in our community. + +## Licensing + +The code in this repository is licensed under the [MIT license](LICENSE). + +## Resources + +- [Writing CodeQL queries](https://help.semmle.com/QL/learn-ql/ql/writing-queries/writing-queries.html) +- [Learning CodeQL](https://help.semmle.com/QL/learn-ql/index.html) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..58767720 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +If you discover a security issue in this repo, please submit it through the [GitHub Security Bug Bounty](https://hackerone.com/github). + +Thanks for helping make CodeQL safe for everyone. \ No newline at end of file diff --git a/alert_weighting.properties b/alert_weighting.properties new file mode 100644 index 00000000..0b7b5d90 --- /dev/null +++ b/alert_weighting.properties @@ -0,0 +1,3 @@ +precision = ("veryhigh", "high", "medium", "low") +severity = ("error", "warning", "recommendation") +security = ("true", "false") diff --git a/build/.gitkeep b/build/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/codeql-extractor.yml b/codeql-extractor.yml new file mode 100644 index 00000000..3e98f064 --- /dev/null +++ b/codeql-extractor.yml @@ -0,0 +1,16 @@ +name: "go" +display_name: "Go" +version: 0.1.0 +pull_request_triggers: + - "**/go.mod" + - "**/glide.yaml" + - "**/Gopkg.toml" +column_kind: "utf8" +extra_env_vars: + SOURCE_ARCHIVE: ${env.CODEQL_EXTRACTOR_GO_SOURCE_ARCHIVE_DIR} + TRAP_FOLDER: ${env.CODEQL_EXTRACTOR_GO_TRAP_DIR} +file_types: + - name: go + display_name: Go + extensions: + - .go diff --git a/codeql-tools/autobuild.cmd b/codeql-tools/autobuild.cmd new file mode 100644 index 00000000..17f9022a --- /dev/null +++ b/codeql-tools/autobuild.cmd @@ -0,0 +1,10 @@ +@echo off +SETLOCAL EnableDelayedExpansion + +rem Some legacy environment variables for the autobuilder. +set LGTM_SRC=%CD% + +type NUL && "%CODEQL_EXTRACTOR_GO_ROOT%/tools/%CODEQL_PLATFORM%/go-autobuilder.exe" +exit /b %ERRORLEVEL% + +ENDLOCAL diff --git a/codeql-tools/autobuild.sh b/codeql-tools/autobuild.sh new file mode 100755 index 00000000..e22f3e6f --- /dev/null +++ b/codeql-tools/autobuild.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -eu + +if [ "$CODEQL_PLATFORM" != "linux64" ] && [ "$CODEQL_PLATFORM" != "osx64" ] ; then + echo "Automatic build detection for $CODEQL_PLATFORM is not implemented." + exit 1 +fi + +# Some legacy environment variables used by the autobuilder. +LGTM_SRC="$(pwd)" +export LGTM_SRC + +"$CODEQL_EXTRACTOR_GO_ROOT/tools/$CODEQL_PLATFORM/go-autobuilder" diff --git a/extractor/cli/go-autobuilder/go-autobuilder.go b/extractor/cli/go-autobuilder/go-autobuilder.go new file mode 100644 index 00000000..b064b7d2 --- /dev/null +++ b/extractor/cli/go-autobuilder/go-autobuilder.go @@ -0,0 +1,350 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net/url" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "strings" +) + +func usage() { + fmt.Fprintf(os.Stderr, + `%s is a wrapper script that installs dependencies and calls the extractor. + +When LGTM_SRC is not set, the script installs dependencies as described below, and then invokes the +extractor in the working directory. + +If LGTM_SRC is set, it checks for the presence of the files 'go.mod', 'Gopkg.toml', and +'glide.yaml' to determine how to install dependencies: if a 'Gopkg.toml' file is present, it uses +'dep ensure', if there is a 'glide.yaml' it uses 'glide install', and otherwise 'go get'. +Additionally, unless a 'go.mod' file is detected, it sets up a temporary GOPATH and moves all +source files into a folder corresponding to the package's import path before installing +dependencies. + +This behavior can be further customized using environment variables: setting LGTM_INDEX_NEED_GOPATH +to 'false' disables the GOPATH set-up, LGTM_INDEX_BUILD_COMMAND can be set to a newline-separated +list of commands to run in order to install dependencies, and LGTM_INDEX_IMPORT_PATH can be used to override the package import path, which is otherwise inferred from the SEMMLE_REPO_URL environment +variable. +`, + os.Args[0]) + fmt.Fprintf(os.Stderr, "Usage:\n\n %s\n", os.Args[0]) +} + +func fileExists(filename string) bool { + _, err := os.Stat(filename) + if err != nil && !os.IsNotExist(err) { + log.Printf("Unable to stat %s: %s\n", filename, err.Error()) + } + return err == nil +} + +func getImportPath() (importpath string) { + importpath = os.Getenv("LGTM_INDEX_IMPORT_PATH") + if importpath == "" { + repourl := os.Getenv("SEMMLE_REPO_URL") + if repourl == "" { + return "" + } + importpath = getImportPathFromRepoURL(repourl) + } + log.Printf("Import path is %s\n", importpath) + return +} + +func getImportPathFromRepoURL(repourl string) string { + // check for scp-like URL as in "git@github.com:Semmle/go.git" + shorturl := regexp.MustCompile("^([^@]+@)?([^:]+):([^/].*?)(\\.git)?$") + m := shorturl.FindStringSubmatch(repourl) + if m != nil { + return m[2] + "/" + m[3] + } + + // otherwise parse as proper URL + u, err := url.Parse(repourl) + if err != nil { + log.Fatalf("Malformed repository URL %s.\n", repourl) + } + host := u.Hostname() + path := u.Path + // strip off leading slashes and trailing `.git` if present + path = regexp.MustCompile("^/+|\\.git$").ReplaceAllString(path, "") + return host + "/" + path +} + +// DependencyInstallerMode is an enum describing how dependencies should be installed +type DependencyInstallerMode int + +const ( + // GoGetNoModules represents dependency installation using `go get` without modules + GoGetNoModules DependencyInstallerMode = iota + // GoGetWithModules represents dependency installation using `go get` with modules + GoGetWithModules + // Dep represent dependency installation using `dep ensure` + Dep + // Glide represents dependency installation using `glide install` + Glide +) + +func main() { + if len(os.Args) > 1 { + usage() + os.Exit(2) + } + + srcdir := os.Getenv("LGTM_SRC") + inLGTM := srcdir != "" + if inLGTM { + log.Printf("LGTM_SRC is %s\n", srcdir) + } else { + cwd, err := os.Getwd() + if err != nil { + log.Fatalln("Failed to get current working directory.") + } + log.Printf("LGTM_SRC is not set; defaulting to current working directory %s\n", cwd) + srcdir = cwd + } + + // we set `SEMMLE_PATH_TRANSFORMER` ourselves in some cases, so blank it out first for consistency + os.Setenv("SEMMLE_PATH_TRANSFORMER", "") + + // determine how to install dependencies and whether a GOPATH needs to be set up before + // extraction + depMode := GoGetNoModules + needGopath := true + if fileExists("go.mod") { + depMode = GoGetWithModules + needGopath = false + log.Println("Found go.mod, enabling go modules") + } else if fileExists("Gopkg.toml") { + depMode = Dep + log.Println("Found Gopkg.toml, using dep instead of go get") + } else if fileExists("glide.yaml") { + depMode = Glide + log.Println("Found glide.yaml, enabling go modules") + } + + // if `LGTM_INDEX_NEED_GOPATH` is set, it overrides the value for `needGopath` inferred above + if needGopathOverride := os.Getenv("LGTM_INDEX_NEED_GOPATH"); needGopathOverride != "" { + inLGTM = true + if needGopathOverride == "true" { + needGopath = true + } else if needGopathOverride == "false" { + needGopath = false + } else { + log.Fatalf("Unexpected value for Boolean environment variable LGTM_NEED_GOPATH: %v.\n", needGopathOverride) + } + } + + importpath := getImportPath() + if needGopath && importpath == "" { + log.Printf("Failed to determine import path, not setting up GOPATH") + needGopath = false + } + + if inLGTM && needGopath { + // a temporary directory where everything is moved while the correct + // directory structure is created. + scratch, err := ioutil.TempDir(srcdir, "scratch") + if err != nil { + log.Fatalf("Failed to create temporary directory %s in directory %s: %s\n", + scratch, srcdir, err.Error()) + } + log.Printf("Temporary directory is %s.\n", scratch) + + // move all files in `srcdir` to `scratch` + dir, err := os.Open(srcdir) + if err != nil { + log.Fatalf("Failed to open source directory %s for reading: %s\n", srcdir, err.Error()) + } + files, err := dir.Readdirnames(-1) + if err != nil { + log.Fatalf("Failed to read source directory %s: %s\n", srcdir, err.Error()) + } + for _, file := range files { + if file != filepath.Base(scratch) { + log.Printf("Moving %s/%s to %s/%s.\n", srcdir, file, scratch, file) + err := os.Rename(filepath.Join(srcdir, file), filepath.Join(scratch, file)) + if err != nil { + log.Fatalf("Failed to move file %s to the temporary directory: %s\n", file, err.Error()) + } + } + } + + // create a new folder which we will add to GOPATH below + root := filepath.Join(srcdir, "root") + + // move source files to where Go expects them to be + newdir := filepath.Join(root, "src", importpath) + err = os.MkdirAll(filepath.Dir(newdir), 0755) + if err != nil { + log.Fatalf("Failed to create directory %s: %s\n", newdir, err.Error()) + } + log.Printf("Moving %s to %s.\n", scratch, newdir) + err = os.Rename(scratch, newdir) + if err != nil { + log.Fatalf("Failed to rename %s to %s: %s\n", scratch, newdir, err.Error()) + } + err = os.Chdir(newdir) + if err != nil { + log.Fatalf("Failed to chdir into %s: %s\n", newdir, err.Error()) + } + + // set up SEMMLE_PATH_TRANSFORMER to ensure paths in the source archive and the snapshot + // match the original source location, not the location we moved it to + pt, err := ioutil.TempFile("", "path-transformer") + if err != nil { + log.Fatalf("Unable to create path transformer file: %s.", err.Error()) + } + defer os.Remove(pt.Name()) + _, err = pt.WriteString("#" + srcdir + "\n" + newdir + "//\n") + if err != nil { + log.Fatalf("Unable to write path transformer file: %s.", err.Error()) + } + err = pt.Close() + if err != nil { + log.Fatalf("Unable to close path transformer file: %s.", err.Error()) + } + err = os.Setenv("SEMMLE_PATH_TRANSFORMER", pt.Name()) + if err != nil { + log.Fatalf("Unable to set SEMMLE_PATH_TRANSFORMER environment variable: %s.\n", err.Error()) + } + + // set/extend GOPATH + oldGopath := os.Getenv("GOPATH") + var newGopath string + if oldGopath != "" { + newGopath = strings.Join( + []string{root, oldGopath}, + string(os.PathListSeparator), + ) + } else { + newGopath = root + } + err = os.Setenv("GOPATH", newGopath) + if err != nil { + log.Fatalf("Unable to set GOPATH to %s: %s\n", newGopath, err.Error()) + } + log.Printf("GOPATH set to %s.\n", newGopath) + } + + // install dependencies + inst := os.Getenv("LGTM_INDEX_BUILD_COMMAND") + var install *exec.Cmd + if inst == "" { + // automatically determine command to install dependencies + + if depMode == Dep { + // set up the dep cache if SEMMLE_CACHE is set + cacheDir := os.Getenv("SEMMLE_CACHE") + if cacheDir != "" { + depCacheDir := filepath.Join(cacheDir, "go", "dep") + log.Printf("Attempting to create dep cache dir %s\n", depCacheDir) + err := os.MkdirAll(depCacheDir, 0755) + if err != nil { + log.Printf("Failed to create dep cache directory: %s\n", err.Error()) + } else { + log.Printf("Setting dep cache directory to %s\n", depCacheDir) + err = os.Setenv("DEPCACHEDIR", depCacheDir) + if err != nil { + log.Println("Failed to set dep cache directory") + } else { + err = os.Setenv("DEPCACHEAGE", "720h") // 30 days + if err != nil { + log.Println("Failed to set dep cache age") + } + } + } + } + + if fileExists("Gopkg.lock") { + // if Gopkg.lock exists, don't update it and only vendor dependencies + install = exec.Command("dep", "ensure", "-v", "-vendor-only") + } else { + install = exec.Command("dep", "ensure", "-v") + } + log.Println("Installing dependencies using `dep ensure`.") + } else if depMode == Glide { + install = exec.Command("glide", "install") + log.Println("Installing dependencies using `glide install`") + } else { + if depMode == GoGetWithModules { + // enable go modules if used + os.Setenv("GO111MODULE", "on") + } + + // get dependencies + install = exec.Command("go", "get", "-v", "./...") + log.Println("Installing dependencies using `go get -v ./...`.") + } + } else { + // write custom build commands into a script, then run it + var ( + ext = "" + header = "" + footer = "" + ) + if runtime.GOOS == "windows" { + ext = ".cmd" + header = "@echo on\n@prompt +$S\n" + footer = "\nIF %ERRORLEVEL% NEQ 0 EXIT" + } else { + ext = ".sh" + header = "#! /bin/bash\nset -xe +u\n" + } + script, err := ioutil.TempFile("", "go-build-command-*"+ext) + if err != nil { + log.Fatalf("Unable to create temporary script holding custom build commands: %s\n", err.Error()) + } + defer os.Remove(script.Name()) + _, err = script.WriteString(header + inst + footer) + if err != nil { + log.Fatalf("Unable to write to temporary script holding custom build commands: %s\n", err.Error()) + } + err = script.Close() + if err != nil { + log.Fatalf("Unable to close temporary script holding custom build commands: %s\n", err.Error()) + } + os.Chmod(script.Name(), 0700) + install = exec.Command(script.Name()) + log.Println("Installing dependencies using custom build command.") + } + + if install != nil { + install.Stdout = os.Stdout + install.Stderr = os.Stderr + err := install.Run() + if err != nil { + log.Printf("Installation of dependencies failed, continuing anyway: %s\n", err.Error()) + } + } + + // extract + mypath, err := os.Executable() + if err != nil { + log.Fatalf("Could not determine path of autobuilder: %v.\n", err) + } + extractor := filepath.Join(filepath.Dir(mypath), "go-extractor") + if runtime.GOOS == "windows" { + extractor = extractor + ".exe" + } + + cwd, err := os.Getwd() + if err != nil { + log.Fatalf("Unable to determine current directory: %s\n", err.Error()) + } + log.Printf("Running extractor command '%s ./...' from directory '%s'.\n", extractor, cwd) + + cmd := exec.Command(extractor, "./...") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + log.Fatalf("Extraction failed: %s\n", err.Error()) + } +} diff --git a/extractor/cli/go-autobuilder/go-autobuilder_test.go b/extractor/cli/go-autobuilder/go-autobuilder_test.go new file mode 100644 index 00000000..939cba8d --- /dev/null +++ b/extractor/cli/go-autobuilder/go-autobuilder_test.go @@ -0,0 +1,19 @@ +package main + +import "testing" + +func TestGetImportPathFromRepoURL(t *testing.T) { + tests := map[string]string{ + "git@github.com:Semmle/go.git": "github.com/Semmle/go", + "git@github.com:Semmle/go": "github.com/Semmle/go", + "https://github.com/Semmle/go.git": "github.com/Semmle/go", + "https://github.com:12345/Semmle/go": "github.com/Semmle/go", + "gitolite@some.url:some/repo": "some.url/some/repo", + } + for input, expected := range tests { + actual := getImportPathFromRepoURL(input) + if actual != expected { + t.Errorf("Expected getImportPathFromRepoURL(\"%s\") to be \"%s\", but got \"%s\".", input, expected, actual) + } + } +} diff --git a/extractor/cli/go-bootstrap/go-bootstrap.go b/extractor/cli/go-bootstrap/go-bootstrap.go new file mode 100644 index 00000000..ff158d57 --- /dev/null +++ b/extractor/cli/go-bootstrap/go-bootstrap.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "regexp" +) + +// A utility program for generating `project` and `variable` files for SemmleCore Go projects +// +// This program should not normally be run directly; it is usually executed as part of +// `odasa bootstrap`, and expects two files as arguments: a (partial) `variables` file and +// an empty file to be filled in with an `` element containing build steps. +// +// The `variables` file is extended with a definition of `LGTM_SRC` and, if it defines the +// `repository` variable, `SEMMLE_REPO_URL`. The only build step is an invocation of the +// Go autobuilder. +func main() { + vars := os.Args[1] + buildSteps := os.Args[2] + + haveRepo := false + content, err := ioutil.ReadFile(vars) + if err != nil { + log.Fatal(err) + } + re := regexp.MustCompile(`(^|\n)repository=`) + haveRepo = re.Find(content) != nil + + additionalVars := "LGTM_SRC=${src}\n" + if haveRepo { + additionalVars += "SEMMLE_REPO_URL=${repository}\n" + } + content = append(content, []byte(additionalVars)...) + err = ioutil.WriteFile(vars, content, 0644) + if err != nil { + log.Fatal(err) + } + + export := "LGTM_SRC" + if haveRepo { + export += ",SEMMLE_REPO_URL" + } + content = []byte(fmt.Sprintf(` + ${semmle_dist}/language-packs/go/tools/platform/${semmle_platform}/bin/go-autobuilder + +`, export)) + err = ioutil.WriteFile(buildSteps, content, 0644) +} diff --git a/extractor/cli/go-extractor/go-extractor.go b/extractor/cli/go-extractor/go-extractor.go new file mode 100644 index 00000000..402e8a6f --- /dev/null +++ b/extractor/cli/go-extractor/go-extractor.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/Semmle/go/extractor/dbscheme" + + "github.com/Semmle/go/extractor" +) + +func usage() { + fmt.Fprintf(os.Stderr, "%s is a program for building a snapshot of a Go code base.\n\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Usage:\n\n %s [...] [...] [--] ...\n\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Flags:\n\n") + fmt.Fprintf(os.Stderr, "--help Print this help.\n") + fmt.Fprintf(os.Stderr, "--dbscheme string Write dbscheme to this file.\n") +} + +func parseFlags(args []string) ([]string, []string, string) { + i := 0 + var dumpDbscheme string + buildFlags := []string{} + for i < len(args) && strings.HasPrefix(args[i], "-") { + if args[i] == "--" { + i += 1 + break + } + + if strings.HasPrefix(args[i], "--dbscheme=") { + dumpDbscheme = strings.TrimPrefix(args[i], "--dbscheme=") + } else if args[i] == "--dbscheme" { + i += 1 + dumpDbscheme = args[i] + } else if args[i] == "--help" { + usage() + os.Exit(0) + } else { + buildFlags = append(buildFlags, args[i]) + } + + i += 1 + } + + return buildFlags, args[i:], dumpDbscheme +} + +func main() { + buildFlags, patterns, dumpDbscheme := parseFlags(os.Args[1:]) + + if dumpDbscheme != "" { + f, err := os.Create(dumpDbscheme) + if err != nil { + log.Fatalf("Unable to open file %s for writing.", dumpDbscheme) + } + dbscheme.PrintDbScheme(f) + f.Close() + log.Printf("Dbscheme written to file %s.", dumpDbscheme) + } + + if len(patterns) == 0 { + log.Println("Nothing to extract.") + } else { + err := extractor.ExtractWithFlags(buildFlags, patterns) + if err != nil { + log.Fatal(err) + } + } +} diff --git a/extractor/cli/go-tokenizer/go-tokenizer.go b/extractor/cli/go-tokenizer/go-tokenizer.go new file mode 100644 index 00000000..ec4a4057 --- /dev/null +++ b/extractor/cli/go-tokenizer/go-tokenizer.go @@ -0,0 +1,57 @@ +package main + +import ( + "encoding/csv" + "flag" + "fmt" + "go/scanner" + "go/token" + "io/ioutil" + "log" + "os" + "strings" +) + +func main() { + flag.Parse() + + fs := token.NewFileSet() + csv := csv.NewWriter(os.Stdout) + defer csv.Flush() + + for _, fileName := range flag.Args() { + src, err := ioutil.ReadFile(fileName) + if err != nil { + log.Fatalf("Unable to read file %s.", fileName) + } + f := fs.AddFile(fileName, -1, len(src)) + + var s scanner.Scanner + s.Init(f, src, nil, 0) + for { + beginPos, tok, text := s.Scan() + + if strings.TrimSpace(text) != "" { + var fuzzyText string + if tok.IsLiteral() { + fuzzyText = tok.String() + } else { + fuzzyText = text + } + + endPos := f.Pos(f.Offset(beginPos) + len(text)) + beginLine := fmt.Sprintf("%d", f.Position(beginPos).Line) + beginColumn := fmt.Sprintf("%d", f.Position(beginPos).Column) + endLine := fmt.Sprintf("%d", f.Position(endPos).Line) + endColumn := fmt.Sprintf("%d", f.Position(endPos).Column) + err = csv.Write([]string{text, fuzzyText, beginLine, beginColumn, endLine, endColumn}) + if err != nil { + log.Fatalf("Unable to write CSV data: %v", err) + } + } + if tok == token.EOF { + break + } + } + } +} diff --git a/extractor/dbscheme/dbscheme.go b/extractor/dbscheme/dbscheme.go new file mode 100644 index 00000000..400d3535 --- /dev/null +++ b/extractor/dbscheme/dbscheme.go @@ -0,0 +1,412 @@ +package dbscheme + +import ( + "fmt" + "io" + "log" + "reflect" + "strings" + + "github.com/Semmle/go/extractor/trap" +) + +// A Type represents a database type +type Type interface { + def() string + ref() string + repr() string + valid(val interface{}) bool +} + +// A PrimitiveType represents a primitive dataase type +type PrimitiveType int + +const ( + // INT represents the primitive database type `int` + INT PrimitiveType = iota + // FLOAT represents the primitive database type `float` + FLOAT + // BOOLEAN represents the primitive database type `boolean` + BOOLEAN + // DATE represents the primitive database type `date` + DATE + // STRING represents the primitive database type `string` + STRING +) + +// A PrimaryKeyType represents a database type defined by a primary key column +type PrimaryKeyType struct { + name string +} + +// A UnionType represents a database type defined as the union of other database types +type UnionType struct { + name string + components []Type +} + +// An AliasType represents a database type which is an alias of another database type +type AliasType struct { + name string + underlying Type +} + +// A CaseType represents a database type defined by a primary key column with a supplementary kind column +type CaseType struct { + base Type + column string + branches []*BranchType +} + +// A BranchType represents one branch of a case type +type BranchType struct { + idx int + name string +} + +func (pt PrimitiveType) def() string { + return "" +} + +func (pt PrimitiveType) ref() string { + switch pt { + case INT: + return "int" + case FLOAT: + return "float" + case BOOLEAN: + return "boolean" + case DATE: + return "date" + case STRING: + return "string" + default: + panic(fmt.Sprintf("Unexpected primitive type %d", pt)) + } +} + +func (pt PrimitiveType) repr() string { + switch pt { + case INT: + return "int" + case FLOAT: + return "float" + case BOOLEAN: + return "boolean" + case DATE: + return "date" + case STRING: + return "string" + default: + panic(fmt.Sprintf("Unexpected primitive type %d", pt)) + } +} + +func (pt PrimitiveType) valid(value interface{}) bool { + switch value.(type) { + case int: + return pt == INT + case float64: + return pt == FLOAT + case bool: + return pt == BOOLEAN + case string: + return pt == STRING + } + return false +} + +func (pkt PrimaryKeyType) def() string { + return "" +} + +func (pkt PrimaryKeyType) ref() string { + return pkt.name +} + +func (pkt PrimaryKeyType) repr() string { + return "int" +} + +func (pkt PrimaryKeyType) valid(value interface{}) bool { + _, ok := value.(trap.Label) + return ok +} + +func (ut UnionType) def() string { + var b strings.Builder + nl := 0 + fmt.Fprintf(&b, "%s = ", ut.name) + for i, comp := range ut.components { + if i > 0 { + if i < len(ut.components)-1 && b.Len()-nl > 100 { + fmt.Fprintf(&b, "\n%s", strings.Repeat(" ", len(ut.name))) + nl = b.Len() + } + fmt.Fprint(&b, " | ") + } + fmt.Fprint(&b, comp.ref()) + } + fmt.Fprint(&b, ";") + return b.String() +} + +func (ut UnionType) ref() string { + return ut.name +} + +func (ut UnionType) repr() string { + return "int" +} + +func (ut UnionType) valid(value interface{}) bool { + _, ok := value.(trap.Label) + return ok +} + +func (at AliasType) def() string { + return at.name + " = " + at.underlying.ref() + ";" +} + +func (at AliasType) ref() string { + return at.name +} + +func (at AliasType) repr() string { + return at.underlying.repr() +} + +func (at AliasType) valid(value interface{}) bool { + return at.underlying.valid(value) +} + +func (ct CaseType) def() string { + var b strings.Builder + fmt.Fprintf(&b, "case %s.%s of", ct.base.ref(), ct.column) + sep := " " + for _, branch := range ct.branches { + fmt.Fprintf(&b, "\n%s%s", sep, branch.def()) + sep = "| " + } + fmt.Fprint(&b, ";") + return b.String() +} + +func (ct CaseType) ref() string { + panic("case types do not have a name") +} + +func (ct CaseType) repr() string { + return "int" +} + +func (ct CaseType) valid(value interface{}) bool { + _, ok := value.(trap.Label) + return ok +} + +func (bt BranchType) def() string { + return fmt.Sprintf("%d = %s", bt.idx, bt.name) +} + +func (bt BranchType) ref() string { + return bt.name +} + +func (bt BranchType) repr() string { + return "int" +} + +func (bt BranchType) valid(value interface{}) bool { + _, ok := value.(trap.Label) + return ok +} + +// Index returns the numeric index of this branch type +func (bt BranchType) Index() int { + return bt.idx +} + +// A Column represents a column in a database table +type Column struct { + columnName string + columnType Type + unique bool + ref bool +} + +func (col Column) String() string { + var b strings.Builder + if col.unique { + fmt.Fprint(&b, "unique ") + } + fmt.Fprintf(&b, "%s %s: %s", col.columnType.repr(), col.columnName, col.columnType.ref()) + if col.ref { + fmt.Fprint(&b, " ref") + } + return b.String() +} + +// Key returns a new column that is the same as this column, but has the `key` flag set to `true` +func (col Column) Key() Column { + return Column{col.columnName, col.columnType, true, false} +} + +// Unique returns a new column that is the same as this column, but has the `unique` flag set to `true` +func (col Column) Unique() Column { + return Column{col.columnName, col.columnType, true, col.ref} +} + +// EntityColumn constructs a column with name `columnName` holding entities of type `columnType` +func EntityColumn(columnType Type, columnName string) Column { + return Column{columnName, columnType, false, true} +} + +// StringColumn constructs a column with name `columnName` holding string values +func StringColumn(columnName string) Column { + return Column{columnName, STRING, false, true} +} + +// IntColumn constructs a column with name `columnName` holding integer values +func IntColumn(columnName string) Column { + return Column{columnName, INT, false, true} +} + +// A Table represents a database table +type Table struct { + name string + schema []Column + keysets [][]string +} + +// KeySet adds `keys` as a keyset to this table +func (tbl *Table) KeySet(keys ...string) *Table { + tbl.keysets = append(tbl.keysets, keys) + return tbl +} + +func (tbl Table) String() string { + var b strings.Builder + for _, keyset := range tbl.keysets { + fmt.Fprint(&b, "#keyset[") + sep := "" + for _, key := range keyset { + fmt.Fprintf(&b, "%s%s", sep, key) + sep = ", " + } + fmt.Fprint(&b, "]\n") + } + fmt.Fprint(&b, tbl.name) + fmt.Fprint(&b, "(") + nl := 0 + for i, column := range tbl.schema { + if i > 0 { + // wrap >100 char lines + if i < len(tbl.schema)-1 && b.Len()-nl > 100 { + fmt.Fprintf(&b, ",\n%s", strings.Repeat(" ", len(tbl.name)+1)) + nl = b.Len() + } else { + fmt.Fprint(&b, ", ") + } + } + fmt.Fprint(&b, column.String()) + } + fmt.Fprint(&b, ");") + return b.String() +} + +// Emit outputs a tuple of `values` for this table using trap writer `tw` +// and panicks if the tuple does not have the right schema +func (tbl Table) Emit(tw *trap.Writer, values ...interface{}) { + if ncol, nval := len(tbl.schema), len(values); ncol != nval { + log.Fatalf("wrong number of values for table %s; expected %d, but got %d", tbl.name, ncol, nval) + } + for i, col := range tbl.schema { + if !col.columnType.valid(values[i]) { + panic(fmt.Sprintf("Invalid value for column %d of table %s; expected a %s, but got %s which is a %s", i, tbl.name, col.columnType.ref(), values[i], reflect.TypeOf(values[i]))) + } + } + tw.Emit(tbl.name, values) +} + +var tables = []*Table{} +var types = []Type{} +var defaultSnippets = []string{} + +// NewTable constructs a new table with the given `name` and `columns` +func NewTable(name string, columns ...Column) *Table { + tbl := &Table{name, columns, [][]string{}} + tables = append(tables, tbl) + return tbl +} + +// NewPrimaryKeyType constructs a new primary key type with the given `name`, +// and adds it to the union types `parents` (if any) +func NewPrimaryKeyType(name string, parents ...*UnionType) *PrimaryKeyType { + tp := &PrimaryKeyType{name} + types = append(types, tp) + for _, parent := range parents { + parent.components = append(parent.components, tp) + } + return tp +} + +// NewUnionType constructs a new union type with the given `name`, +// and adds it to the union types `parents` (if any) +func NewUnionType(name string, parents ...*UnionType) *UnionType { + tp := &UnionType{name, []Type{}} + types = append(types, tp) + for _, parent := range parents { + parent.components = append(parent.components, tp) + } + return tp +} + +// NewAliasType constructs a new alias type with the given `name` that aliases `underlying` +func NewAliasType(name string, underlying Type) *AliasType { + tp := &AliasType{name, underlying} + types = append(types, tp) + return tp +} + +// NewCaseType constructs a new case type on the given `base` type whose discriminator values +// come from `column` +func NewCaseType(base Type, column string) *CaseType { + tp := &CaseType{base, column, []*BranchType{}} + types = append(types, tp) + return tp +} + +// NewBranch adds a new branch with the given `name` to this case type +// and adds it to the union types `parents` (if any) +func (ct *CaseType) NewBranch(name string, parents ...*UnionType) *BranchType { + tp := &BranchType{len(ct.branches), name} + ct.branches = append(ct.branches, tp) + for _, parent := range parents { + parent.components = append(parent.components, tp) + } + return tp +} + +// AddDefaultSnippet adds the given text `snippet` to the schema of this database +func AddDefaultSnippet(snippet string) bool { + defaultSnippets = append(defaultSnippets, snippet) + return true +} + +// PrintDbScheme prints the schema of this database to the writer `w` +func PrintDbScheme(w io.Writer) { + fmt.Fprintf(w, "/** Auto-generated dbscheme; do not edit. */\n\n") + for _, snippet := range defaultSnippets { + fmt.Fprintf(w, "%s\n", snippet) + } + for _, table := range tables { + fmt.Fprintf(w, "%s\n\n", table.String()) + } + for _, tp := range types { + def := tp.def() + if def != "" { + fmt.Fprintf(w, "%s\n\n", def) + } + } +} diff --git a/extractor/dbscheme/tables.go b/extractor/dbscheme/tables.go new file mode 100644 index 00000000..7fdb08f6 --- /dev/null +++ b/extractor/dbscheme/tables.go @@ -0,0 +1,872 @@ +package dbscheme + +import ( + "go/ast" + "go/token" + gotypes "go/types" +) + +var defaultSnippet = AddDefaultSnippet(` +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); +`) + +// ContainerType is the type of files and folders +var ContainerType = NewUnionType("@container") + +// LocatableType is the type of program entities that have locations +var LocatableType = NewUnionType("@locatable") + +// NodeType is the type of AST nodes +var NodeType = NewUnionType("@node", LocatableType) + +// DocumentableType is the type of AST nodes to which documentation can be attached +var DocumentableType = NewUnionType("@documentable", NodeType) + +// ExprParentType is the type of AST nodes that can have expressions as children +var ExprParentType = NewUnionType("@exprparent", NodeType) + +// FieldParentType is the type of AST nodes that can have fields as children +var FieldParentType = NewUnionType("@fieldparent", NodeType) + +// StmtParentType is the type of AST nodes that can have statements as children +var StmtParentType = NewUnionType("@stmtparent", NodeType) + +// DeclParentType is the type of AST nodes that can have declarations as children +var DeclParentType = NewUnionType("@declparent", NodeType) + +// FuncDefType is the type of AST nodes that define functions, that is, function +// declarations and function literals +var FuncDefType = NewUnionType("@funcdef", StmtParentType, ExprParentType) + +// ScopeNodeType is the type of AST nodes that mapy have a scope attached to them +var ScopeNodeType = NewUnionType("@scopenode", NodeType) + +// LocationDefaultType is the type of source locations +var LocationDefaultType = NewPrimaryKeyType("@location_default") + +// FileType is the type of file AST nodes +var FileType = NewPrimaryKeyType("@file", ContainerType, DocumentableType, ExprParentType, DeclParentType, ScopeNodeType) + +// FolderType is the type of folders +var FolderType = NewPrimaryKeyType("@folder", ContainerType) + +// CommentGroupType is the type of comment groups +var CommentGroupType = NewPrimaryKeyType("@comment_group", NodeType) + +// CommentType is the type of comments +var CommentType = NewPrimaryKeyType("@comment", NodeType) + +// ExprType is the type of expression AST nodes +var ExprType = NewPrimaryKeyType("@expr", ExprParentType) + +// FieldType is the type of field AST nodes +var FieldType = NewPrimaryKeyType("@field", DocumentableType, ExprParentType) + +// StmtType is the type of statement AST nodes +var StmtType = NewPrimaryKeyType("@stmt", ExprParentType, StmtParentType) + +// DeclType is the type of declaration AST nodes +var DeclType = NewPrimaryKeyType("@decl", ExprParentType, StmtParentType, FieldParentType) + +// SpecType is the type of spec AST nodes +var SpecType = NewPrimaryKeyType("@spec", ExprParentType, DocumentableType) + +// TypeType is the type of types +var TypeType = NewPrimaryKeyType("@type") + +// LocationType is an alias for LocationDefaultType +var LocationType = NewAliasType("@location", LocationDefaultType) + +// SourceLineType is an alias for LocatableType +var SourceLineType = NewAliasType("@sourceline", LocatableType) + +// CommentKind is a case type for distinguishing different kinds of comments +var CommentKind = NewCaseType(CommentType, "kind") + +// SlashSlashComment is the type of single-line comments starting with a double slash +var SlashSlashComment = CommentKind.NewBranch("@slashslashcomment") + +// SlashStarComment is the type of block comments delimited by stars and slashes +var SlashStarComment = CommentKind.NewBranch("@slashstarcomment") + +// ExprKind is a case type for distinguishing different kinds of expression AST nodes +var ExprKind = NewCaseType(ExprType, "kind") + +// BadExpr is type of bad (that is, unparseable) expression AST nodes +var BadExpr = ExprKind.NewBranch("@badexpr") + +// IdentExpr is the type of identifier expression AST nodes +var IdentExpr = ExprKind.NewBranch("@ident") + +// EllipsisExpr is the type of ellipsis expression AST nodes +var EllipsisExpr = ExprKind.NewBranch("@ellipsis") + +// BasicLitExpr is the type of basic (that is, primitive) literal expression AST nodes +var BasicLitExpr = NewUnionType("@basiclit") + +// IntLitExpr is a case type for dishinguishing different kinds of literal expression AST nodes +var IntLitExpr = ExprKind.NewBranch("@intlit", BasicLitExpr) + +// FloatLitExpr is the type of floating-point literal expression AST nodes +var FloatLitExpr = ExprKind.NewBranch("@floatlit", BasicLitExpr) + +// ImagLitExpr is the type of imaginary literal expression AST nodes +var ImagLitExpr = ExprKind.NewBranch("@imaglit", BasicLitExpr) + +// CharLitExpr is the type of character literal expression AST nodes +var CharLitExpr = ExprKind.NewBranch("@charlit", BasicLitExpr) + +// StringLitExpr is the type of string literal expression AST nodes +var StringLitExpr = ExprKind.NewBranch("@stringlit", BasicLitExpr) + +// FuncLitExpr is the type of function literal expression AST nodes +var FuncLitExpr = ExprKind.NewBranch("@funclit", FuncDefType) + +// CompositeLitExpr is the type of composite literal expression AST nodes +var CompositeLitExpr = ExprKind.NewBranch("@compositelit") + +// ParenExpr is the type of parenthesis expression AST nodes +var ParenExpr = ExprKind.NewBranch("@parenexpr") + +// SelectorExpr is the type of selector expression AST nodes +var SelectorExpr = ExprKind.NewBranch("@selectorexpr") + +// IndexExpr is the type of index expression AST nodes +var IndexExpr = ExprKind.NewBranch("@indexexpr") + +// SliceExpr is the type of slice expression AST nodes +var SliceExpr = ExprKind.NewBranch("@sliceexpr") + +// TypeAssertExpr is the type of type assertion expression AST nodes +var TypeAssertExpr = ExprKind.NewBranch("@typeassertexpr") + +// CallOrConversionExpr is the type of call and conversion expression AST nodes +// (which cannot be distinguished by purely syntactic criteria) +var CallOrConversionExpr = ExprKind.NewBranch("@callorconversionexpr") + +// StarExpr is the type of star expression AST nodes +var StarExpr = ExprKind.NewBranch("@starexpr") + +// OperatorExpr is the type of operator expression AST nodes +var OperatorExpr = NewUnionType("@operatorexpr") + +// LogicalExpr is the type of logical operator expression AST nodes +var LogicalExpr = NewUnionType("@logicalexpr", OperatorExpr) + +// ArithmeticExpr is the type of arithmetic operator expression AST nodes +var ArithmeticExpr = NewUnionType("@arithmeticexpr", OperatorExpr) + +// BitwiseExpr is the type of bitwise operator expression AST nodes +var BitwiseExpr = NewUnionType("@bitwiseexpr", OperatorExpr) + +// UnaryExpr is the type of unary operator expression AST nodes +var UnaryExpr = NewUnionType("@unaryexpr", OperatorExpr) + +// LogicalUnaryExpr is the type of logical unary operator expression AST nodes +var LogicalUnaryExpr = NewUnionType("@logicalunaryexpr", UnaryExpr, LogicalExpr) + +// BitwiseUnaryExpr is the type of bitwise unary operator expression AST nodes +var BitwiseUnaryExpr = NewUnionType("@bitwiseunaryexpr", UnaryExpr, BitwiseExpr) + +// ArithmeticUnaryExpr is the type of arithmetic unary operator expression AST nodes +var ArithmeticUnaryExpr = NewUnionType("@arithmeticunaryexpr", UnaryExpr, ArithmeticExpr) + +// BinaryExpr is the type of binary operator expression AST nodes +var BinaryExpr = NewUnionType("@binaryexpr", OperatorExpr) + +// LogicalBinaryExpr is the type of logical binary operator expression AST nodes +var LogicalBinaryExpr = NewUnionType("@logicalbinaryexpr", BinaryExpr, LogicalExpr) + +// BitwiseBinaryExpr is the type of bitwise binary operator expression AST nodes +var BitwiseBinaryExpr = NewUnionType("@bitwisebinaryexpr", BinaryExpr, BitwiseExpr) + +// ArithmeticBinaryExpr is the type of arithmetic binary operator expression AST nodes +var ArithmeticBinaryExpr = NewUnionType("@arithmeticbinaryexpr", BinaryExpr, ArithmeticExpr) + +// ShiftExpr is the type of shift operator expression AST nodes +var ShiftExpr = NewUnionType("@shiftexpr", BitwiseBinaryExpr) + +// Comparison is the type of comparison operator expression AST nodes +var Comparison = NewUnionType("@comparison", BinaryExpr) + +// EqualityTest is the type of equality operator expression AST nodes +var EqualityTest = NewUnionType("@equalitytest", Comparison) + +// RelationalComparison is the type of relational operator expression AST nodes +var RelationalComparison = NewUnionType("@relationalcomparison", Comparison) + +// KeyValueExpr is the type of key-value expression AST nodes +var KeyValueExpr = ExprKind.NewBranch("@keyvalueexpr") + +// ArrayTypeExpr is the type of array type AST nodes +var ArrayTypeExpr = ExprKind.NewBranch("@arraytypeexpr") + +// StructTypeExpr is the type of struct type AST nodes +var StructTypeExpr = ExprKind.NewBranch("@structtypeexpr", FieldParentType) + +// FuncTypeExpr is the type of function type AST nodes +var FuncTypeExpr = ExprKind.NewBranch("@functypeexpr", FieldParentType, ScopeNodeType) + +// InterfaceTypeExpr is the type of interface type AST nodes +var InterfaceTypeExpr = ExprKind.NewBranch("@interfacetypeexpr", FieldParentType) + +// MapTypeExpr is the type of map type AST nodes +var MapTypeExpr = ExprKind.NewBranch("@maptypeexpr") + +// ChanTypeExpr is the type of channel type AST nodes +var ChanTypeExpr = NewUnionType("@chantypeexpr") + +// UnaryExprs is a map from unary operator tokens to the corresponding AST node type +var UnaryExprs = map[token.Token]*BranchType{ + token.ADD: ExprKind.NewBranch("@plusexpr", ArithmeticUnaryExpr), + token.SUB: ExprKind.NewBranch("@minusexpr", ArithmeticUnaryExpr), + token.NOT: ExprKind.NewBranch("@notexpr", LogicalUnaryExpr), + token.XOR: ExprKind.NewBranch("@complementexpr", BitwiseUnaryExpr), + token.MUL: ExprKind.NewBranch("@derefexpr", UnaryExpr), + token.AND: ExprKind.NewBranch("@addressexpr", UnaryExpr), + token.ARROW: ExprKind.NewBranch("@arrowexpr", UnaryExpr), +} + +// BinaryExprs is a map from binary operator tokens to the corresponding AST node type +var BinaryExprs = map[token.Token]*BranchType{ + token.LOR: ExprKind.NewBranch("@lorexpr", LogicalBinaryExpr), + token.LAND: ExprKind.NewBranch("@landexpr", LogicalBinaryExpr), + token.EQL: ExprKind.NewBranch("@eqlexpr", EqualityTest), + token.NEQ: ExprKind.NewBranch("@neqexpr", EqualityTest), + token.LSS: ExprKind.NewBranch("@lssexpr", RelationalComparison), + token.LEQ: ExprKind.NewBranch("@leqexpr", RelationalComparison), + token.GTR: ExprKind.NewBranch("@gtrexpr", RelationalComparison), + token.GEQ: ExprKind.NewBranch("@geqexpr", RelationalComparison), + token.ADD: ExprKind.NewBranch("@addexpr", ArithmeticBinaryExpr), + token.SUB: ExprKind.NewBranch("@subexpr", ArithmeticBinaryExpr), + token.OR: ExprKind.NewBranch("@orexpr", BitwiseBinaryExpr), + token.XOR: ExprKind.NewBranch("@xorexpr", BitwiseBinaryExpr), + token.MUL: ExprKind.NewBranch("@mulexpr", ArithmeticBinaryExpr), + token.QUO: ExprKind.NewBranch("@quoexpr", ArithmeticBinaryExpr), + token.REM: ExprKind.NewBranch("@remexpr", ArithmeticBinaryExpr), + token.SHL: ExprKind.NewBranch("@shlexpr", ShiftExpr), + token.SHR: ExprKind.NewBranch("@shrexpr", ShiftExpr), + token.AND: ExprKind.NewBranch("@andexpr", BitwiseBinaryExpr), + token.AND_NOT: ExprKind.NewBranch("@andnotexpr", BitwiseBinaryExpr), +} + +// ChanTypeExprs is a map from channel type expressions to the corresponding AST node type +var ChanTypeExprs = map[ast.ChanDir]*BranchType{ + ast.SEND: ExprKind.NewBranch("@sendchantypeexpr", ChanTypeExpr), + ast.RECV: ExprKind.NewBranch("@recvchantypeexpr", ChanTypeExpr), + ast.SEND | ast.RECV: ExprKind.NewBranch("@sendrcvchantypeexpr", ChanTypeExpr), +} + +// StmtKind is a case type for distinguishing different kinds of statement AST nodes +var StmtKind = NewCaseType(StmtType, "kind") + +// BadStmtType is the type of bad (that is, unparseable) statement AST nodes +var BadStmtType = StmtKind.NewBranch("@badstmt") + +// DeclStmtType is the type of declaration statement AST nodes +var DeclStmtType = StmtKind.NewBranch("@declstmt", DeclParentType) + +// EmptyStmtType is the type of empty statement AST nodes +var EmptyStmtType = StmtKind.NewBranch("@emptystmt") + +// LabeledStmtType is the type of labeled statement AST nodes +var LabeledStmtType = StmtKind.NewBranch("@labeledstmt") + +// ExprStmtType is the type of expressio statemement AST nodes +var ExprStmtType = StmtKind.NewBranch("@exprstmt") + +// SendStmtType is the type of send statement AST nodes +var SendStmtType = StmtKind.NewBranch("@sendstmt") + +// IncDecStmtType is the type of increment/decrement statement AST nodes +var IncDecStmtType = NewUnionType("@incdecstmt") + +// IncStmtType is the type of increment statement AST nodes +var IncStmtType = StmtKind.NewBranch("@incstmt", IncDecStmtType) + +// DecStmtType is the type of decrement statement AST nodes +var DecStmtType = StmtKind.NewBranch("@decstmt", IncDecStmtType) + +// AssignmentType is the type of assignment statement AST nodes +var AssignmentType = NewUnionType("@assignment") + +// SimpleAssignStmtType is the type of simple (i.e., non-compound) assignment statement AST nodes +var SimpleAssignStmtType = NewUnionType("@simpleassignstmt", AssignmentType) + +// CompoundAssignStmtType is the type of compound assignment statement AST nodes +var CompoundAssignStmtType = NewUnionType("@compoundassignstmt", AssignmentType) + +// GoStmtType is the type of go statement AST nodes +var GoStmtType = StmtKind.NewBranch("@gostmt") + +// DeferStmtType is the type of defer statement AST nodes +var DeferStmtType = StmtKind.NewBranch("@deferstmt") + +// ReturnStmtType is the type of return statement AST nodes +var ReturnStmtType = StmtKind.NewBranch("@returnstmt") + +// BranchStmtType is the type of branch statement AST nodes +var BranchStmtType = NewUnionType("@branchstmt") + +// BreakStmtType is the type of break statement AST nodes +var BreakStmtType = StmtKind.NewBranch("@breakstmt", BranchStmtType) + +// ContinueStmtType is the type of continue statement AST nodes +var ContinueStmtType = StmtKind.NewBranch("@continuestmt", BranchStmtType) + +// GotoStmtType is the type of goto statement AST nodes +var GotoStmtType = StmtKind.NewBranch("@gotostmt", BranchStmtType) + +// FallthroughStmtType is the type of fallthrough statement AST nodes +var FallthroughStmtType = StmtKind.NewBranch("@fallthroughstmt", BranchStmtType) + +// BlockStmtType is the type of block statement AST nodes +var BlockStmtType = StmtKind.NewBranch("@blockstmt", ScopeNodeType) + +// IfStmtType is the type of if statement AST nodes +var IfStmtType = StmtKind.NewBranch("@ifstmt", ScopeNodeType) + +// CaseClauseType is the type of case clause AST nodes +var CaseClauseType = StmtKind.NewBranch("@caseclause", ScopeNodeType) + +// SwitchStmtType is the type of switch statement AST nodes, covering both expression switch and type switch +var SwitchStmtType = NewUnionType("@switchstmt", ScopeNodeType) + +// ExprSwitchStmtType is the type of expression-switch statement AST nodes +var ExprSwitchStmtType = StmtKind.NewBranch("@exprswitchstmt", SwitchStmtType) + +// TypeSwitchStmtType is the type of type-switch statement AST nodes +var TypeSwitchStmtType = StmtKind.NewBranch("@typeswitchstmt", SwitchStmtType) + +// CommClauseType is the type of comm clause AST ndoes +var CommClauseType = StmtKind.NewBranch("@commclause", ScopeNodeType) + +// SelectStmtType is the type of select statement AST nodes +var SelectStmtType = StmtKind.NewBranch("@selectstmt") + +// LoopStmtType is the type of loop statement AST nodes (including for statements and range statements) +var LoopStmtType = NewUnionType("@loopstmt", ScopeNodeType) + +// ForStmtType is the type of for statement AST nodes +var ForStmtType = StmtKind.NewBranch("@forstmt", LoopStmtType) + +// RangeStmtType is the type of range statement AST nodes +var RangeStmtType = StmtKind.NewBranch("@rangestmt", LoopStmtType) + +// AssignStmtTypes is a map from assignmnt operator tokens to corresponding AST node types +var AssignStmtTypes = map[token.Token]*BranchType{ + token.ASSIGN: StmtKind.NewBranch("@assignstmt", SimpleAssignStmtType), + token.DEFINE: StmtKind.NewBranch("@definestmt", SimpleAssignStmtType), + token.ADD_ASSIGN: StmtKind.NewBranch("@addassignstmt", CompoundAssignStmtType), + token.SUB_ASSIGN: StmtKind.NewBranch("@subassignstmt", CompoundAssignStmtType), + token.MUL_ASSIGN: StmtKind.NewBranch("@mulassignstmt", CompoundAssignStmtType), + token.QUO_ASSIGN: StmtKind.NewBranch("@quoassignstmt", CompoundAssignStmtType), + token.REM_ASSIGN: StmtKind.NewBranch("@remassignstmt", CompoundAssignStmtType), + token.AND_ASSIGN: StmtKind.NewBranch("@andassignstmt", CompoundAssignStmtType), + token.OR_ASSIGN: StmtKind.NewBranch("@orassignstmt", CompoundAssignStmtType), + token.XOR_ASSIGN: StmtKind.NewBranch("@xorassignstmt", CompoundAssignStmtType), + token.SHL_ASSIGN: StmtKind.NewBranch("@shlassignstmt", CompoundAssignStmtType), + token.SHR_ASSIGN: StmtKind.NewBranch("@shrassignstmt", CompoundAssignStmtType), + token.AND_NOT_ASSIGN: StmtKind.NewBranch("@andnotassignstmt", CompoundAssignStmtType), +} + +// DeclKind is a case type for distinguishing different kinds of declaration AST nodes +var DeclKind = NewCaseType(DeclType, "kind") + +// BadDeclType is the type of bad (that is, unparseable) declaration AST nodes +var BadDeclType = DeclKind.NewBranch("@baddecl") + +// GenDeclType is the type of generic declaration AST nodes +var GenDeclType = NewUnionType("@gendecl", DocumentableType) + +// ImportDeclType is the type of import declaration AST nodes +var ImportDeclType = DeclKind.NewBranch("@importdecl", GenDeclType) + +// ConstDeclType is the type of constant declaration AST nodes +var ConstDeclType = DeclKind.NewBranch("@constdecl", GenDeclType) + +// TypeDeclType is the type of type declaration AST nodes +var TypeDeclType = DeclKind.NewBranch("@typedecl", GenDeclType) + +// VarDeclType is the type of variable declaration AST nodes +var VarDeclType = DeclKind.NewBranch("@vardecl", GenDeclType) + +// FuncDeclType is the type of function declaration AST nodes +var FuncDeclType = DeclKind.NewBranch("@funcdecl", DocumentableType, FuncDefType) + +// SpecKind is a case type for distinguishing different kinds of declaration specification nodes +var SpecKind = NewCaseType(SpecType, "kind") + +// ImportSpecType is the type of import declaration specification nodes +var ImportSpecType = SpecKind.NewBranch("@importspec") + +// ValueSpecType is the type of value declaration specification nodes +var ValueSpecType = SpecKind.NewBranch("@valuespec") + +// TypeSpecType is the type of type declaration specification nodes +var TypeSpecType = SpecKind.NewBranch("@typespec") + +// ObjectType is the type of objects (that is, declared entities) +var ObjectType = NewPrimaryKeyType("@object") + +// ObjectKind is a case type for distinguishing different kinds of built-in and declared objects +var ObjectKind = NewCaseType(ObjectType, "kind") + +// DeclObjectType is the type of declared objects +var DeclObjectType = NewUnionType("@declobject") + +// BuiltinObjectType is the type of built-in objects +var BuiltinObjectType = NewUnionType("@builtinobject") + +// PkgObjectType is the type of imported packages +var PkgObjectType = ObjectKind.NewBranch("@pkgobject") + +// TypeObjectType is the type of declared or built-in named types +var TypeObjectType = NewUnionType("@typeobject") + +// DeclTypeObjectType is the type of declared named types +var DeclTypeObjectType = ObjectKind.NewBranch("@decltypeobject", TypeObjectType, DeclObjectType) + +// BuiltinTypeObjectType is the type of built-in named types +var BuiltinTypeObjectType = ObjectKind.NewBranch("@builtintypeobject", TypeObjectType, BuiltinObjectType) + +// ValueObjectType is the type of declared or built-in variables or constants +var ValueObjectType = NewUnionType("@valueobject") + +// ConstObjectType is the type of declared or built-in constants +var ConstObjectType = NewUnionType("@constobject", ValueObjectType) + +// DeclConstObjectType is the type of declared constants +var DeclConstObjectType = ObjectKind.NewBranch("@declconstobject", ConstObjectType, DeclObjectType) + +// BuiltinConstObjectType is the type of built-in constants +var BuiltinConstObjectType = ObjectKind.NewBranch("@builtinconstobject", ConstObjectType, BuiltinObjectType) + +// VarObjectType is the type of declared or built-in variables (the latter do not currently exist) +var VarObjectType = NewUnionType("@varobject", ValueObjectType) + +// DeclVarObjectType is the type of declared variables including function parameters, results and struct fields +var DeclVarObjectType = ObjectKind.NewBranch("@declvarobject", VarObjectType, DeclObjectType) + +// FunctionObjectType is the type of declared or built-in functions +var FunctionObjectType = NewUnionType("@functionobject", ValueObjectType) + +// DeclFuncObjectType is the type of declared functions, including (abstract and concrete) methods +var DeclFuncObjectType = ObjectKind.NewBranch("@declfunctionobject", FunctionObjectType, DeclObjectType) + +// BuiltinFuncObjectType is the type of built-in functions +var BuiltinFuncObjectType = ObjectKind.NewBranch("@builtinfunctionobject", FunctionObjectType, BuiltinObjectType) + +// LabelObjectType is the type of statement labels +var LabelObjectType = ObjectKind.NewBranch("@labelobject") + +// ScopeType is the type of scopes +var ScopeType = NewPrimaryKeyType("@scope") + +// ScopeKind is a case type for distinguishing different kinds of scopes +var ScopeKind = NewCaseType(ScopeType, "kind") + +// UniverseScopeType is the type of the universe scope +var UniverseScopeType = ScopeKind.NewBranch("@universescope") + +// PackageScopeType is the type of package scopes +var PackageScopeType = ScopeKind.NewBranch("@packagescope") + +// LocalScopeType is the type of local (that is, non-universe, non-package) scopes +var LocalScopeType = ScopeKind.NewBranch("@localscope", LocatableType) + +// TypeKind is a case type for distinguishing different kinds of types +var TypeKind = NewCaseType(TypeType, "kind") + +// BasicType is the union of all basic types +var BasicType = NewUnionType("@basictype") + +// BoolType is the union of the normal and literal bool types +var BoolType = NewUnionType("@booltype", BasicType) + +// NumericType is the union of numeric types +var NumericType = NewUnionType("@numerictype", BasicType) + +// IntegerType is the union of integer types +var IntegerType = NewUnionType("@integertype", NumericType) + +// SignedIntegerType is the union of signed integer types +var SignedIntegerType = NewUnionType("@signedintegertype", IntegerType) + +// UnsignedIntegerType is the union of unsigned integer types +var UnsignedIntegerType = NewUnionType("@unsignedintegertype", IntegerType) + +// FloatType is the union of floating-point types +var FloatType = NewUnionType("@floattype", NumericType) + +// ComplexType is the union of complex types +var ComplexType = NewUnionType("@complextype", NumericType) + +// StringType is the union of the normal and literal string types +var StringType = NewUnionType("@stringtype", BasicType) + +// LiteralType is the union of literal types +var LiteralType = NewUnionType("@literaltype", BasicType) + +// BasicTypes is a map from basic type kinds to the corresponding entity types +var BasicTypes = map[gotypes.BasicKind]*BranchType{ + gotypes.Invalid: TypeKind.NewBranch("@invalidtype", BasicType), + gotypes.Bool: TypeKind.NewBranch("@boolexprtype", BoolType), + gotypes.Int: TypeKind.NewBranch("@inttype", SignedIntegerType), + gotypes.Int8: TypeKind.NewBranch("@int8type", SignedIntegerType), + gotypes.Int16: TypeKind.NewBranch("@int16type", SignedIntegerType), + gotypes.Int32: TypeKind.NewBranch("@int32type", SignedIntegerType), + gotypes.Int64: TypeKind.NewBranch("@int64type", SignedIntegerType), + gotypes.Uint: TypeKind.NewBranch("@uinttype", UnsignedIntegerType), + gotypes.Uint8: TypeKind.NewBranch("@uint8type", UnsignedIntegerType), + gotypes.Uint16: TypeKind.NewBranch("@uint16type", UnsignedIntegerType), + gotypes.Uint32: TypeKind.NewBranch("@uint32type", UnsignedIntegerType), + gotypes.Uint64: TypeKind.NewBranch("@uint64type", UnsignedIntegerType), + gotypes.Uintptr: TypeKind.NewBranch("@uintptrtype", BasicType), + gotypes.Float32: TypeKind.NewBranch("@float32type", FloatType), + gotypes.Float64: TypeKind.NewBranch("@float64type", FloatType), + gotypes.Complex64: TypeKind.NewBranch("@complex64type", ComplexType), + gotypes.Complex128: TypeKind.NewBranch("@complex128type", ComplexType), + gotypes.String: TypeKind.NewBranch("@stringexprtype", StringType), + gotypes.UnsafePointer: TypeKind.NewBranch("@unsafepointertype", BasicType), + gotypes.UntypedBool: TypeKind.NewBranch("@boolliteraltype", LiteralType, BoolType), + gotypes.UntypedInt: TypeKind.NewBranch("@intliteraltype", LiteralType, SignedIntegerType), + gotypes.UntypedRune: TypeKind.NewBranch("@runeliteraltype", LiteralType, SignedIntegerType), + gotypes.UntypedFloat: TypeKind.NewBranch("@floatliteraltype", LiteralType, FloatType), + gotypes.UntypedComplex: TypeKind.NewBranch("@complexliteraltype", LiteralType, ComplexType), + gotypes.UntypedString: TypeKind.NewBranch("@stringliteraltype", LiteralType, StringType), + gotypes.UntypedNil: TypeKind.NewBranch("@nilliteraltype", LiteralType), +} + +// CompositeType is the type of all composite (that is, non-basic) types +var CompositeType = NewUnionType("@compositetype") + +// ElementContainerType is the type of types that have elements, such as arrays +// and channels +var ElementContainerType = NewUnionType("@containertype", CompositeType) + +// ArrayType is the type of array types +var ArrayType = TypeKind.NewBranch("@arraytype", ElementContainerType) + +// SliceType is the type of slice types +var SliceType = TypeKind.NewBranch("@slicetype", ElementContainerType) + +// StructType is the type of struct types +var StructType = TypeKind.NewBranch("@structtype", CompositeType) + +// PointerType is the type of pointer types +var PointerType = TypeKind.NewBranch("@pointertype", CompositeType) + +// InterfaceType is the type of interface types +var InterfaceType = TypeKind.NewBranch("@interfacetype", CompositeType) + +// TupleType is the type of tuple types +var TupleType = TypeKind.NewBranch("@tupletype", CompositeType) + +// SignatureType is the type of signature types +var SignatureType = TypeKind.NewBranch("@signaturetype", CompositeType) + +// MapType is the type of map types +var MapType = TypeKind.NewBranch("@maptype", ElementContainerType) + +// ChanType is the type of channel types +var ChanType = NewUnionType("@chantype", ElementContainerType) + +// ChanTypes is a map from channel type directions to the corresponding type +var ChanTypes = map[gotypes.ChanDir]*BranchType{ + gotypes.SendOnly: TypeKind.NewBranch("@sendchantype", ChanType), + gotypes.RecvOnly: TypeKind.NewBranch("@recvchantype", ChanType), + gotypes.SendRecv: TypeKind.NewBranch("@sendrcvchantype", ChanType), +} + +// NamedType is the type of named types +var NamedType = TypeKind.NewBranch("@namedtype", CompositeType) + +// PackageType is the type of packages +var PackageType = NewPrimaryKeyType("@package") + +// LocationsDefaultTable is the table defining location objects +var LocationsDefaultTable = NewTable("locations_default", + EntityColumn(LocationDefaultType, "id").Key(), + EntityColumn(FileType, "file"), + IntColumn("beginLine"), + IntColumn("beginColumn"), + IntColumn("endLine"), + IntColumn("endColumn"), +) + +// NumlinesTable is the table containing LoC information +var NumlinesTable = NewTable("numlines", + EntityColumn(SourceLineType, "element_id"), + IntColumn("num_lines"), + IntColumn("num_code"), + IntColumn("num_comment"), +) + +// FilesTable is the table defining file nodes +var FilesTable = NewTable("files", + EntityColumn(FileType, "id").Key(), + StringColumn("name"), + StringColumn("simple"), + StringColumn("ext"), + IntColumn("fromSource"), +) + +// FoldersTable is the table defining folder entities +var FoldersTable = NewTable("folders", + EntityColumn(FolderType, "id").Key(), + StringColumn("name"), + StringColumn("simple"), +) + +// ContainerParentTable is the table defining the parent-child relation among container entities +var ContainerParentTable = NewTable("containerparent", + EntityColumn(ContainerType, "parent"), + EntityColumn(ContainerType, "child").Unique(), +) + +// HasLocationTable is the table associating entities with their locations +var HasLocationTable = NewTable("has_location", + EntityColumn(LocatableType, "locatable").Unique(), + EntityColumn(LocationType, "location"), +) + +// CommentGroupsTable is the table defining comment group entities +var CommentGroupsTable = NewTable("comment_groups", + EntityColumn(CommentGroupType, "id").Key(), +) + +// CommentsTable is the table defining comment entities +var CommentsTable = NewTable("comments", + EntityColumn(CommentType, "id").Key(), + IntColumn("kind"), + EntityColumn(CommentGroupType, "parent"), + IntColumn("idx"), + StringColumn("text"), +) + +// DocCommentsTable is the table associating doc comments with the nodes they document +var DocCommentsTable = NewTable("doc_comments", + EntityColumn(DocumentableType, "node").Unique(), + EntityColumn(CommentGroupType, "comment"), +) + +// ExprsTable is the table defininig expression AST nodes +var ExprsTable = NewTable("exprs", + EntityColumn(ExprType, "id").Key(), + IntColumn("kind"), + EntityColumn(ExprParentType, "parent"), + IntColumn("idx"), +).KeySet("parent", "idx") + +// LiteralsTable is the table associating literal expression AST nodes with their values +var LiteralsTable = NewTable("literals", + EntityColumn(ExprType, "expr").Unique(), + StringColumn("value"), + StringColumn("raw"), +) + +// ConstValuesTable is the table associating constant expressions with their values +var ConstValuesTable = NewTable("constvalues", + EntityColumn(ExprType, "expr").Unique(), + StringColumn("value"), + StringColumn("exact"), +) + +// FieldsTable is the table defining field AST nodes +var FieldsTable = NewTable("fields", + EntityColumn(FieldType, "id").Key(), + EntityColumn(FieldParentType, "parent"), + IntColumn("idx"), +) + +// StmtsTable is the table defining statement AST nodes +var StmtsTable = NewTable("stmts", + EntityColumn(StmtType, "id").Key(), + IntColumn("kind"), + EntityColumn(StmtParentType, "parent"), + IntColumn("idx"), +).KeySet("parent", "idx") + +// DeclsTable is the table defining declaration AST nodes +var DeclsTable = NewTable("decls", + EntityColumn(DeclType, "id").Key(), + IntColumn("kind"), + EntityColumn(DeclParentType, "parent"), + IntColumn("idx"), +).KeySet("parent", "idx") + +// SpecsTable is the table defining declaration specification AST nodes +var SpecsTable = NewTable("specs", + EntityColumn(SpecType, "id").Key(), + IntColumn("kind"), + EntityColumn(GenDeclType, "parent"), + IntColumn("idx"), +).KeySet("parent", "idx") + +// ScopesTable is the table defining scopes +var ScopesTable = NewTable("scopes", + EntityColumn(ScopeType, "id").Key(), + IntColumn("kind"), +) + +// ScopeNestingTable is the table describing scope nesting +var ScopeNestingTable = NewTable("scopenesting", + EntityColumn(ScopeType, "inner").Unique(), + EntityColumn(ScopeType, "outer"), +) + +// ScopeNodesTable is the table associating local scopes with the AST nodes that induce them +var ScopeNodesTable = NewTable("scopenodes", + EntityColumn(ScopeNodeType, "node").Unique(), + EntityColumn(LocalScopeType, "scope"), +) + +// ObjectsTable is the table describing objects (that is, declared entities) +var ObjectsTable = NewTable("objects", + EntityColumn(ObjectType, "id").Key(), + IntColumn("kind"), + StringColumn("name"), +) + +// ObjectScopesTable is the table describing the scope to which an object belongs (if any) +var ObjectScopesTable = NewTable("objectscopes", + EntityColumn(ObjectType, "object").Unique(), + EntityColumn(ScopeType, "scope"), +) + +// ObjectTypesTable is the table describing the type of an object (if any) +var ObjectTypesTable = NewTable("objecttypes", + EntityColumn(ObjectType, "object").Unique(), + EntityColumn(TypeType, "tp"), +) + +// MethodReceiversTable maps methods to their receiver +var MethodReceiversTable = NewTable("methodreceivers", + EntityColumn(ObjectType, "method").Unique(), + EntityColumn(ObjectType, "receiver"), +) + +// FieldStructsTable maps fields to the structs they are in +var FieldStructsTable = NewTable("fieldstructs", + EntityColumn(ObjectType, "field").Unique(), + EntityColumn(StructType, "struct"), +) + +// DefsTable maps identifiers to the objects they define +var DefsTable = NewTable("defs", + EntityColumn(IdentExpr, "ident"), + EntityColumn(ObjectType, "object"), +) + +// UsesTable maps identifiers to the objects they denote +var UsesTable = NewTable("uses", + EntityColumn(IdentExpr, "ident"), + EntityColumn(ObjectType, "object"), +) + +// TypesTable is the table describing types +var TypesTable = NewTable("types", + EntityColumn(TypeType, "id").Key(), + IntColumn("kind"), +) + +// TypeOfTable is the table associating expressions with their types (if known) +var TypeOfTable = NewTable("type_of", + EntityColumn(ExprType, "expr").Unique(), + EntityColumn(TypeType, "tp"), +) + +// TypeNameTable is the table associating named types with their names +var TypeNameTable = NewTable("typename", + EntityColumn(TypeType, "tp").Unique(), + StringColumn("name"), +) + +// KeyTypeTable is the table associating maps with their key type +var KeyTypeTable = NewTable("key_type", + EntityColumn(MapType, "map").Unique(), + EntityColumn(TypeType, "tp"), +) + +// ElementTypeTable is the table associating container types with their element +// type +var ElementTypeTable = NewTable("element_type", + EntityColumn(ElementContainerType, "container").Unique(), + EntityColumn(TypeType, "tp"), +) + +// BaseTypeTable is the table associating pointer types with their base type +var BaseTypeTable = NewTable("base_type", + EntityColumn(PointerType, "ptr").Unique(), + EntityColumn(TypeType, "tp"), +) + +// UnderlyingTypeTable is the table associating named types with their +// underlying type +var UnderlyingTypeTable = NewTable("underlying_type", + EntityColumn(NamedType, "named").Unique(), + EntityColumn(TypeType, "tp"), +) + +// ComponentTypesTable is the table associating composite types with their component types +var ComponentTypesTable = NewTable("component_types", + EntityColumn(CompositeType, "parent"), + IntColumn("index"), + StringColumn("name"), + EntityColumn(TypeType, "tp"), +).KeySet("parent", "index") + +// ArrayLengthTable is the table associating array types with their length (represented as a string +// since Go array lengths are 64-bit and hence do not always fit into a QL integer) +var ArrayLengthTable = NewTable("array_length", + EntityColumn(ArrayType, "tp").Unique(), + StringColumn("len"), +) + +// TypeObjectTable maps types to their corresponding objects, if any +var TypeObjectTable = NewTable("type_objects", + EntityColumn(TypeType, "tp").Unique(), + EntityColumn(ObjectType, "object"), +) + +// PackagesTable is the table describing packages +var PackagesTable = NewTable("packages", + EntityColumn(PackageType, "id").Key(), + StringColumn("name"), + StringColumn("path"), + EntityColumn(PackageScopeType, "scope"), +) diff --git a/extractor/extractor.go b/extractor/extractor.go new file mode 100644 index 00000000..aeb2ebe2 --- /dev/null +++ b/extractor/extractor.go @@ -0,0 +1,1305 @@ +package extractor + +import ( + "fmt" + "go/ast" + "go/constant" + "go/scanner" + "go/token" + "go/types" + "io/ioutil" + "log" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "github.com/Semmle/go/extractor/dbscheme" + "github.com/Semmle/go/extractor/srcarchive" + "github.com/Semmle/go/extractor/trap" + "golang.org/x/tools/go/packages" +) + +// Extract extracts the packages specified by the given patterns +func Extract(patterns []string) error { + return ExtractWithFlags(nil, patterns) +} + +// ExtractWithFlags extracts the packages specified by the given patterns and build flags +func ExtractWithFlags(buildFlags []string, patterns []string) error { + cfg := &packages.Config{ + Mode: packages.NeedName | packages.NeedFiles | + packages.NeedCompiledGoFiles | + packages.NeedImports | packages.NeedDeps | + packages.NeedTypes | packages.NeedTypesSizes | + packages.NeedTypesInfo | packages.NeedSyntax, + BuildFlags: buildFlags, + } + pkgs, err := packages.Load(cfg, patterns...) + + if err != nil { + return err + } + + if len(pkgs) == 0 { + log.Printf("No packages found.") + } + + extractUniverseScope() + + // recursively visit all packages in depth-first order; + // on the way down, associate each package scope with its corresponding package, + // and on the way up extract the package's scope + packages.Visit(pkgs, func(pkg *packages.Package) bool { + return true + }, func(pkg *packages.Package) { + if len(pkg.Errors) != 0 { + log.Printf("Warning: encountered errors extracting package `%s`:", pkg.PkgPath) + for _, err := range pkg.Errors { + log.Printf(" %s", err.Error()) + } + } + + tw, err := trap.NewWriter(pkg.PkgPath, pkg) + if err != nil { + log.Fatal(err) + } + defer tw.Close() + + scope := extractPackageScope(tw, pkg) + tw.ForEachObject(extractObjectType) + lbl := tw.Labeler.GlobalID(pkg.PkgPath + ";pkg") + dbscheme.PackagesTable.Emit(tw, lbl, pkg.Name, pkg.PkgPath, scope) + }) + + // this sets the number of threads that the Go runtime will spawn; this is separate + // from the number of goroutines that the program spawns, which are scheduled into + // the system threads by the Go runtime scheduler + threads := os.Getenv("LGTM_THREADS") + if maxprocs, err := strconv.Atoi(threads); err == nil && maxprocs > 0 { + log.Printf("Max threads set to %d", maxprocs) + runtime.GOMAXPROCS(maxprocs) + } else if threads != "" { + log.Printf("Warning: LGTM_THREADS value %s is not valid, defaulting to using all available threads.", threads) + } + // if the value is empty or not set, use the Go default, which is the number of cores + // available since Go 1.5, but is subject to change + + var maxgoroutines int + if maxgoroutines, err = strconv.Atoi(os.Getenv("SEMMLE_MAX_GOROUTINES")); err != nil { + maxgoroutines = 32 + } else { + log.Printf("Max goroutines set to %d", maxgoroutines) + } + + var wg sync.WaitGroup + // this semaphore is used to limit the number of files that are open at once; + // this is to prevent the extractor from running into issues with caps on the + // number of open files that can be held by one process + fdSem := newSemaphore(100) + // this semaphore is used to limit the number of goroutines spawned, so we + // don't run into memory issues + goroutineSem := newSemaphore(maxgoroutines) + + // extract AST information for all packages + for _, pkg := range pkgs { + extractPackage(pkg, &wg, goroutineSem, fdSem) + } + + wg.Wait() + + return nil +} + +// extractUniverseScope extracts symbol table information for the universe scope +func extractUniverseScope() { + tw, err := trap.NewWriter("universe", nil) + if err != nil { + log.Fatal(err) + } + defer tw.Close() + + lbl := tw.Labeler.ScopeID(types.Universe, nil) + dbscheme.ScopesTable.Emit(tw, lbl, dbscheme.UniverseScopeType.Index()) + extractObjects(tw, types.Universe, lbl) +} + +// extractObjects extracts all objects declared in the given scope +func extractObjects(tw *trap.Writer, scope *types.Scope, scopeLabel trap.Label) { + for _, name := range scope.Names() { + obj := scope.Lookup(name) + lbl, exists := tw.Labeler.ScopedObjectID(obj, extractType(tw, obj.Type())) + if !exists { + extractObject(tw, obj, lbl) + } + + if obj.Parent() != scope { + // this can happen if a scope is embedded into another with a `.` import. + continue + } + dbscheme.ObjectScopesTable.Emit(tw, lbl, scopeLabel) + } +} + +// extractObject extracts a single object and emits it to the objects table. +func extractObject(tw *trap.Writer, obj types.Object, lbl trap.Label) { + name := obj.Name() + isBuiltin := obj.Parent() == types.Universe + var kind int + switch obj.(type) { + case *types.PkgName: + kind = dbscheme.PkgObjectType.Index() + case *types.TypeName: + if isBuiltin { + kind = dbscheme.BuiltinTypeObjectType.Index() + } else { + kind = dbscheme.DeclTypeObjectType.Index() + } + case *types.Const: + if isBuiltin { + kind = dbscheme.BuiltinConstObjectType.Index() + } else { + kind = dbscheme.DeclConstObjectType.Index() + } + case *types.Nil: + kind = dbscheme.BuiltinConstObjectType.Index() + case *types.Var: + kind = dbscheme.DeclVarObjectType.Index() + case *types.Builtin: + kind = dbscheme.BuiltinFuncObjectType.Index() + case *types.Func: + kind = dbscheme.DeclFuncObjectType.Index() + case *types.Label: + kind = dbscheme.LabelObjectType.Index() + default: + log.Fatalf("unknown object of type %T", obj) + } + dbscheme.ObjectsTable.Emit(tw, lbl, kind, name) + + // for methods, additionally extract information about the receiver + if sig, ok := obj.Type().(*types.Signature); ok { + if recv := sig.Recv(); recv != nil { + recvlbl, exists := tw.Labeler.ReceiverObjectID(recv, lbl) + if !exists { + extractObject(tw, recv, recvlbl) + } + dbscheme.MethodReceiversTable.Emit(tw, lbl, recvlbl) + } + } +} + +// extractObjectType extracts type and receiver information for a given object +func extractObjectType(tw *trap.Writer, obj types.Object, lbl trap.Label) { + if tp := obj.Type(); tp != nil { + dbscheme.ObjectTypesTable.Emit(tw, lbl, extractType(tw, tp)) + } +} + +// extractPackage extracts AST information for all files in the given package +func extractPackage(pkg *packages.Package, wg *sync.WaitGroup, + goroutineSem *semaphore, fdSem *semaphore) { + for _, astFile := range pkg.Syntax { + wg.Add(1) + goroutineSem.acquire(1) + go func(astFile *ast.File) { + err := extractFile(astFile, pkg, fdSem) + if err != nil { + log.Fatal(err) + } + goroutineSem.release(1) + wg.Done() + }(astFile) + } +} + +// normalizedPath computes the normalized path (with symlinks resolved) for the given file +func normalizedPath(ast *ast.File, fset *token.FileSet) string { + file := fset.File(ast.Package).Name() + path, err := filepath.EvalSymlinks(file) + if err != nil { + return file + } + return path +} + +// extractFile extracts AST information for the given file +func extractFile(ast *ast.File, pkg *packages.Package, fdSem *semaphore) error { + fset := pkg.Fset + path := normalizedPath(ast, fset) + + fdSem.acquire(3) + + log.Printf("Extracting %s", path) + start := time.Now() + + defer fdSem.release(1) + tw, err := trap.NewWriter(path, pkg) + if err != nil { + fdSem.release(2) + return err + } + defer tw.Close() + + err = srcarchive.Add(path) + fdSem.release(2) + if err != nil { + return err + } + + extractFileInfo(tw, path) + + extractScopes(tw, ast, pkg) + + extractFileNode(tw, ast) + + tw.ForEachObject(extractObjectType) + + extractNumLines(tw, path, ast) + + end := time.Since(start) + log.Printf("Done extracting %s (%dms)", path, end.Nanoseconds()/1000000) + + return nil +} + +// stemAndExt splits a given file name into its stem (the part before the last '.') +// and extension (the part after the last '.') +func stemAndExt(base string) (string, string) { + if i := strings.LastIndexByte(base, '.'); i >= 0 { + return base[:i], base[i+1:] + } + return base, "" +} + +// extractFileInfo extracts file-system level information for the given file, populating +// the `files` and `containerparent` tables +func extractFileInfo(tw *trap.Writer, file string) { + path := filepath.ToSlash(srcarchive.TransformPath(file)) + components := strings.Split(path, "/") + parentPath := "" + var parentLbl trap.Label + for i, component := range components { + if i == 0 { + if component == "" { + path = "/" + } else { + path = component + } + } else { + path = parentPath + "/" + component + } + if i == len(components)-1 { + stem, ext := stemAndExt(component) + lbl := tw.Labeler.FileLabel() + dbscheme.FilesTable.Emit(tw, lbl, path, stem, ext, 0) + dbscheme.ContainerParentTable.Emit(tw, parentLbl, lbl) + extractLocation(tw, lbl, 0, 0, 0, 0) + break + } + lbl := tw.Labeler.GlobalID(path + ";folder") + dbscheme.FoldersTable.Emit(tw, lbl, path, component) + if i > 0 { + dbscheme.ContainerParentTable.Emit(tw, parentLbl, lbl) + } + if path != "/" { + parentPath = path + } + parentLbl = lbl + } +} + +// extractLocation emits a location entity for the given entity +func extractLocation(tw *trap.Writer, entity trap.Label, sl int, sc int, el int, ec int) { + lbl := tw.Labeler.FileLabel() + locLbl := tw.Labeler.GlobalID(fmt.Sprintf("loc,{%s},%d,%d,%d,%d", lbl.String(), sl, sc, el, ec)) + dbscheme.LocationsDefaultTable.Emit(tw, locLbl, lbl, sl, sc, el, ec) + dbscheme.HasLocationTable.Emit(tw, entity, locLbl) +} + +// extractNodeLocation extracts location information for the given node +func extractNodeLocation(tw *trap.Writer, nd ast.Node, lbl trap.Label) { + if nd == nil { + return + } + fset := tw.Package.Fset + start, end := fset.Position(nd.Pos()), fset.Position(nd.End()) + extractLocation(tw, lbl, start.Line, start.Column, end.Line, end.Column-1) +} + +// extractPackageScope extracts symbol table information for the given package +func extractPackageScope(tw *trap.Writer, pkg *packages.Package) trap.Label { + pkgScope := pkg.Types.Scope() + pkgScopeLabel := tw.Labeler.ScopeID(pkgScope, pkg.Types) + dbscheme.ScopesTable.Emit(tw, pkgScopeLabel, dbscheme.PackageScopeType.Index()) + dbscheme.ScopeNestingTable.Emit(tw, pkgScopeLabel, tw.Labeler.ScopeID(types.Universe, nil)) + extractObjects(tw, pkgScope, pkgScopeLabel) + return pkgScopeLabel +} + +// extractScopeLocation extracts location information for the given scope +func extractScopeLocation(tw *trap.Writer, scope *types.Scope, lbl trap.Label) { + fset := tw.Package.Fset + start, end := fset.Position(scope.Pos()), fset.Position(scope.End()) + extractLocation(tw, lbl, start.Line, start.Column, end.Line, end.Column-1) +} + +// extractScopes extracts symbol table information for the package scope and all local scopes +// of the given package +func extractScopes(tw *trap.Writer, nd *ast.File, pkg *packages.Package) { + pkgScopeLabel := extractPackageScope(tw, pkg) + fileScope := pkg.TypesInfo.Scopes[nd] + if fileScope != nil { + extractLocalScope(tw, fileScope, pkgScopeLabel) + } +} + +// extractLocalScope extracts symbol table information for the given scope and all its nested scopes +func extractLocalScope(tw *trap.Writer, scope *types.Scope, parentScopeLabel trap.Label) { + scopeLabel := tw.Labeler.ScopeID(scope, nil) + dbscheme.ScopesTable.Emit(tw, scopeLabel, dbscheme.LocalScopeType.Index()) + extractScopeLocation(tw, scope, scopeLabel) + dbscheme.ScopeNestingTable.Emit(tw, scopeLabel, parentScopeLabel) + + for i := 0; i < scope.NumChildren(); i++ { + childScope := scope.Child(i) + extractLocalScope(tw, childScope, scopeLabel) + } + + extractObjects(tw, scope, scopeLabel) +} + +// extractFileNode extracts AST information for the given file and all nodes contained in it +func extractFileNode(tw *trap.Writer, nd *ast.File) { + lbl := tw.Labeler.FileLabel() + + extractExpr(tw, nd.Name, lbl, 0) + + for i, decl := range nd.Decls { + extractDecl(tw, decl, lbl, i) + } + + for _, cg := range nd.Comments { + extractCommentGroup(tw, cg) + } + + extractDoc(tw, nd.Doc, lbl) + emitScopeNodeInfo(tw, nd, lbl) +} + +// extractDoc extracts information about a doc comment group associated with a given element +func extractDoc(tw *trap.Writer, doc *ast.CommentGroup, elt trap.Label) { + if doc != nil { + dbscheme.DocCommentsTable.Emit(tw, elt, tw.Labeler.LocalID(doc)) + } +} + +// extractCommentGroup extracts information about a doc comment group +func extractCommentGroup(tw *trap.Writer, cg *ast.CommentGroup) { + lbl := tw.Labeler.LocalID(cg) + dbscheme.CommentGroupsTable.Emit(tw, lbl) + extractNodeLocation(tw, cg, lbl) + for i, c := range cg.List { + extractComment(tw, c, lbl, i) + } +} + +// extractComment extracts information about a given comment +func extractComment(tw *trap.Writer, c *ast.Comment, parent trap.Label, idx int) { + lbl := tw.Labeler.LocalID(c) + rawText := c.Text + var kind int + var text string + if rawText[:2] == "//" { + kind = dbscheme.SlashSlashComment.Index() + text = rawText[2:] + } else { + kind = dbscheme.SlashStarComment.Index() + text = rawText[2 : len(rawText)-2] + } + dbscheme.CommentsTable.Emit(tw, lbl, kind, parent, idx, text) + extractNodeLocation(tw, c, lbl) +} + +// emitScopeNodeInfo associates an AST node with its induced scope, if any +func emitScopeNodeInfo(tw *trap.Writer, nd ast.Node, lbl trap.Label) { + scope, exists := tw.Package.TypesInfo.Scopes[nd] + if exists { + dbscheme.ScopeNodesTable.Emit(tw, lbl, tw.Labeler.ScopeID(scope, tw.Package.Types)) + } +} + +// extractExpr extracts AST information for the given expression and all its subexpressions +func extractExpr(tw *trap.Writer, expr ast.Expr, parent trap.Label, idx int) { + if expr == nil { + return + } + + lbl := tw.Labeler.LocalID(expr) + extractTypeOf(tw, expr, lbl) + + var kind int + switch expr := expr.(type) { + case *ast.BadExpr: + kind = dbscheme.BadExpr.Index() + case *ast.Ident: + if expr == nil { + return + } + kind = dbscheme.IdentExpr.Index() + dbscheme.LiteralsTable.Emit(tw, lbl, expr.Name, expr.Name) + def := tw.Package.TypesInfo.Defs[expr] + if def != nil { + defTyp := extractType(tw, def.Type()) + objlbl, exists := tw.Labeler.LookupObjectID(def, defTyp) + if objlbl == trap.InvalidLabel { + log.Printf("Omitting def binding to unknown object %v", def) + } else { + if !exists { + extractObject(tw, def, objlbl) + } + dbscheme.DefsTable.Emit(tw, lbl, objlbl) + } + } + use := tw.Package.TypesInfo.Uses[expr] + if use != nil { + useTyp := extractType(tw, use.Type()) + objlbl, exists := tw.Labeler.LookupObjectID(use, useTyp) + if objlbl == trap.InvalidLabel { + log.Printf("Omitting use binding to unknown object %v", use) + } else { + if !exists { + extractObject(tw, use, objlbl) + } + dbscheme.UsesTable.Emit(tw, lbl, objlbl) + } + } + case *ast.Ellipsis: + if expr == nil { + return + } + kind = dbscheme.EllipsisExpr.Index() + extractExpr(tw, expr.Elt, lbl, 0) + case *ast.BasicLit: + if expr == nil { + return + } + value := "" + switch expr.Kind { + case token.INT: + ival, _ := strconv.ParseInt(expr.Value, 0, 64) + value = strconv.FormatInt(ival, 10) + kind = dbscheme.IntLitExpr.Index() + case token.FLOAT: + value = expr.Value + kind = dbscheme.FloatLitExpr.Index() + case token.IMAG: + value = expr.Value + kind = dbscheme.ImagLitExpr.Index() + case token.CHAR: + value, _ = strconv.Unquote(expr.Value) + kind = dbscheme.CharLitExpr.Index() + case token.STRING: + value, _ = strconv.Unquote(expr.Value) + kind = dbscheme.StringLitExpr.Index() + default: + log.Fatalf("unknown literal kind %v", expr.Kind) + } + dbscheme.LiteralsTable.Emit(tw, lbl, value, expr.Value) + case *ast.FuncLit: + if expr == nil { + return + } + kind = dbscheme.FuncLitExpr.Index() + extractExpr(tw, expr.Type, lbl, 0) + extractStmt(tw, expr.Body, lbl, 1) + case *ast.CompositeLit: + if expr == nil { + return + } + kind = dbscheme.CompositeLitExpr.Index() + extractExpr(tw, expr.Type, lbl, 0) + extractExprs(tw, expr.Elts, lbl, 1, 1) + case *ast.ParenExpr: + if expr == nil { + return + } + kind = dbscheme.ParenExpr.Index() + extractExpr(tw, expr.X, lbl, 0) + case *ast.SelectorExpr: + if expr == nil { + return + } + kind = dbscheme.SelectorExpr.Index() + extractExpr(tw, expr.X, lbl, 0) + extractExpr(tw, expr.Sel, lbl, 1) + case *ast.IndexExpr: + if expr == nil { + return + } + kind = dbscheme.IndexExpr.Index() + extractExpr(tw, expr.X, lbl, 0) + extractExpr(tw, expr.Index, lbl, 1) + case *ast.SliceExpr: + if expr == nil { + return + } + kind = dbscheme.SliceExpr.Index() + extractExpr(tw, expr.X, lbl, 0) + extractExpr(tw, expr.Low, lbl, 1) + extractExpr(tw, expr.High, lbl, 2) + extractExpr(tw, expr.Max, lbl, 3) + case *ast.TypeAssertExpr: + if expr == nil { + return + } + kind = dbscheme.TypeAssertExpr.Index() + extractExpr(tw, expr.X, lbl, 0) + extractExpr(tw, expr.Type, lbl, 1) + case *ast.CallExpr: + if expr == nil { + return + } + kind = dbscheme.CallOrConversionExpr.Index() + extractExpr(tw, expr.Fun, lbl, 0) + extractExprs(tw, expr.Args, lbl, 1, 1) + case *ast.StarExpr: + if expr == nil { + return + } + kind = dbscheme.StarExpr.Index() + extractExpr(tw, expr.X, lbl, 0) + case *ast.KeyValueExpr: + if expr == nil { + return + } + kind = dbscheme.KeyValueExpr.Index() + extractExpr(tw, expr.Key, lbl, 0) + extractExpr(tw, expr.Value, lbl, 1) + case *ast.UnaryExpr: + if expr == nil { + return + } + tp := dbscheme.UnaryExprs[expr.Op] + if tp == nil { + log.Fatalf("unsupported unary operator %s", expr.Op) + } + kind = tp.Index() + extractExpr(tw, expr.X, lbl, 0) + case *ast.BinaryExpr: + if expr == nil { + return + } + tp := dbscheme.BinaryExprs[expr.Op] + if tp == nil { + log.Fatalf("unsupported binary operator %s", expr.Op) + } + kind = tp.Index() + extractExpr(tw, expr.X, lbl, 0) + extractExpr(tw, expr.Y, lbl, 1) + case *ast.ArrayType: + if expr == nil { + return + } + kind = dbscheme.ArrayTypeExpr.Index() + extractExpr(tw, expr.Len, lbl, 0) + extractExpr(tw, expr.Elt, lbl, 1) + case *ast.StructType: + if expr == nil { + return + } + kind = dbscheme.StructTypeExpr.Index() + extractFields(tw, expr.Fields, lbl, 0, 1) + case *ast.FuncType: + if expr == nil { + return + } + kind = dbscheme.FuncTypeExpr.Index() + extractFields(tw, expr.Params, lbl, 0, 1) + extractFields(tw, expr.Results, lbl, -1, -1) + emitScopeNodeInfo(tw, expr, lbl) + case *ast.InterfaceType: + if expr == nil { + return + } + kind = dbscheme.InterfaceTypeExpr.Index() + extractFields(tw, expr.Methods, lbl, 0, 1) + case *ast.MapType: + if expr == nil { + return + } + kind = dbscheme.MapTypeExpr.Index() + extractExpr(tw, expr.Key, lbl, 0) + extractExpr(tw, expr.Value, lbl, 1) + case *ast.ChanType: + if expr == nil { + return + } + tp := dbscheme.ChanTypeExprs[expr.Dir] + if tp == nil { + log.Fatalf("unsupported channel direction %v", expr.Dir) + } + kind = tp.Index() + extractExpr(tw, expr.Value, lbl, 0) + default: + log.Fatalf("unknown expression of type %T", expr) + } + dbscheme.ExprsTable.Emit(tw, lbl, kind, parent, idx) + extractNodeLocation(tw, expr, lbl) + extractValueOf(tw, expr, lbl) +} + +// extractExprs extracts AST information for a list of expressions, which are children of +// the given parent +// `idx` is the index of the first child in the list, and `dir` is the index increment of +// each child over its preceding child (usually either 1 for assigning increasing indices, or +// -1 for decreasing indices) +func extractExprs(tw *trap.Writer, exprs []ast.Expr, parent trap.Label, idx int, dir int) { + if exprs != nil { + for _, expr := range exprs { + extractExpr(tw, expr, parent, idx) + idx += dir + } + } +} + +// extractTypeOf looks up the type of `expr`, extracts it if it hasn't previously been +// extracted, and associates it with `expr` in the `type_of` table +func extractTypeOf(tw *trap.Writer, expr ast.Expr, lbl trap.Label) { + tp := tw.Package.TypesInfo.TypeOf(expr) + if tp != nil { + tplbl := extractType(tw, tp) + dbscheme.TypeOfTable.Emit(tw, lbl, tplbl) + } +} + +// extractValueOf looks up the value of `expr`, and associates it with `expr` in +// the `consts` table +func extractValueOf(tw *trap.Writer, expr ast.Expr, lbl trap.Label) { + tpVal := tw.Package.TypesInfo.Types[expr] + + if tpVal.Value != nil { + // if Value is non-nil, the expression has a constant value + + // note that string literals in import statements do not have an associated + // Value and so do not get added to the table + + var value string + exact := tpVal.Value.ExactString() + switch tpVal.Value.Kind() { + case constant.String: + // we need to unquote strings + value = constant.StringVal(tpVal.Value) + exact = constant.StringVal(tpVal.Value) + case constant.Float: + flval, _ := constant.Float64Val(tpVal.Value) + value = fmt.Sprintf("%.20g", flval) + case constant.Complex: + real, _ := constant.Float64Val(constant.Real(tpVal.Value)) + imag, _ := constant.Float64Val(constant.Imag(tpVal.Value)) + value = fmt.Sprintf("(%.20g + %.20gi)", real, imag) + default: + value = tpVal.Value.ExactString() + } + + dbscheme.ConstValuesTable.Emit(tw, lbl, value, exact) + } else if tpVal.IsNil() { + dbscheme.ConstValuesTable.Emit(tw, lbl, "nil", "nil") + } +} + +// extractFields extracts AST information for a list of fields, which are children of +// the given parent +// `idx` is the index of the first child in the list, and `dir` is the index increment of +// each child over its preceding child (usually either 1 for assigning increasing indices, or +// -1 for decreasing indices) +func extractFields(tw *trap.Writer, fields *ast.FieldList, parent trap.Label, idx int, dir int) { + if fields == nil || fields.List == nil { + return + } + for _, field := range fields.List { + lbl := tw.Labeler.LocalID(field) + dbscheme.FieldsTable.Emit(tw, lbl, parent, idx) + extractNodeLocation(tw, field, lbl) + if field.Names != nil { + for i, name := range field.Names { + extractExpr(tw, name, lbl, i+1) + } + } + extractExpr(tw, field.Type, lbl, 0) + extractExpr(tw, field.Tag, lbl, -1) + extractDoc(tw, field.Doc, lbl) + idx += dir + } +} + +// extractStmt extracts AST information for a given statement and all other statements or expressions +// nested inside it +func extractStmt(tw *trap.Writer, stmt ast.Stmt, parent trap.Label, idx int) { + if stmt == nil { + return + } + + lbl := tw.Labeler.LocalID(stmt) + var kind int + switch stmt := stmt.(type) { + case *ast.BadStmt: + kind = dbscheme.BadStmtType.Index() + case *ast.DeclStmt: + if stmt == nil { + return + } + kind = dbscheme.DeclStmtType.Index() + extractDecl(tw, stmt.Decl, lbl, 0) + case *ast.EmptyStmt: + kind = dbscheme.EmptyStmtType.Index() + case *ast.LabeledStmt: + if stmt == nil { + return + } + kind = dbscheme.LabeledStmtType.Index() + extractExpr(tw, stmt.Label, lbl, 0) + extractStmt(tw, stmt.Stmt, lbl, 1) + case *ast.ExprStmt: + if stmt == nil { + return + } + kind = dbscheme.ExprStmtType.Index() + extractExpr(tw, stmt.X, lbl, 0) + case *ast.SendStmt: + if stmt == nil { + return + } + kind = dbscheme.SendStmtType.Index() + extractExpr(tw, stmt.Chan, lbl, 0) + extractExpr(tw, stmt.Value, lbl, 1) + case *ast.IncDecStmt: + if stmt == nil { + return + } + if stmt.Tok == token.INC { + kind = dbscheme.IncStmtType.Index() + } else if stmt.Tok == token.DEC { + kind = dbscheme.DecStmtType.Index() + } else { + log.Fatalf("unsupported increment/decrement operator %v", stmt.Tok) + } + extractExpr(tw, stmt.X, lbl, 0) + case *ast.AssignStmt: + if stmt == nil { + return + } + tp := dbscheme.AssignStmtTypes[stmt.Tok] + if tp == nil { + log.Fatalf("unsupported assignment statement with operator %v", stmt.Tok) + } + kind = tp.Index() + extractExprs(tw, stmt.Lhs, lbl, -1, -1) + extractExprs(tw, stmt.Rhs, lbl, 1, 1) + case *ast.GoStmt: + if stmt == nil { + return + } + kind = dbscheme.GoStmtType.Index() + extractExpr(tw, stmt.Call, lbl, 0) + case *ast.DeferStmt: + if stmt == nil { + return + } + kind = dbscheme.DeferStmtType.Index() + extractExpr(tw, stmt.Call, lbl, 0) + case *ast.ReturnStmt: + kind = dbscheme.ReturnStmtType.Index() + extractExprs(tw, stmt.Results, lbl, 0, 1) + case *ast.BranchStmt: + if stmt == nil { + return + } + switch stmt.Tok { + case token.BREAK: + kind = dbscheme.BreakStmtType.Index() + case token.CONTINUE: + kind = dbscheme.ContinueStmtType.Index() + case token.GOTO: + kind = dbscheme.GotoStmtType.Index() + case token.FALLTHROUGH: + kind = dbscheme.FallthroughStmtType.Index() + default: + log.Fatalf("unsupported branch statement type %v", stmt.Tok) + } + extractExpr(tw, stmt.Label, lbl, 0) + case *ast.BlockStmt: + if stmt == nil { + return + } + kind = dbscheme.BlockStmtType.Index() + extractStmts(tw, stmt.List, lbl, 0, 1) + emitScopeNodeInfo(tw, stmt, lbl) + case *ast.IfStmt: + if stmt == nil { + return + } + kind = dbscheme.IfStmtType.Index() + extractStmt(tw, stmt.Init, lbl, 0) + extractExpr(tw, stmt.Cond, lbl, 1) + extractStmt(tw, stmt.Body, lbl, 2) + extractStmt(tw, stmt.Else, lbl, 3) + emitScopeNodeInfo(tw, stmt, lbl) + case *ast.CaseClause: + if stmt == nil { + return + } + kind = dbscheme.CaseClauseType.Index() + extractExprs(tw, stmt.List, lbl, -1, -1) + extractStmts(tw, stmt.Body, lbl, 0, 1) + emitScopeNodeInfo(tw, stmt, lbl) + case *ast.SwitchStmt: + if stmt == nil { + return + } + kind = dbscheme.ExprSwitchStmtType.Index() + extractStmt(tw, stmt.Init, lbl, 0) + extractExpr(tw, stmt.Tag, lbl, 1) + extractStmt(tw, stmt.Body, lbl, 2) + emitScopeNodeInfo(tw, stmt, lbl) + case *ast.TypeSwitchStmt: + if stmt == nil { + return + } + kind = dbscheme.TypeSwitchStmtType.Index() + extractStmt(tw, stmt.Init, lbl, 0) + extractStmt(tw, stmt.Assign, lbl, 1) + extractStmt(tw, stmt.Body, lbl, 2) + emitScopeNodeInfo(tw, stmt, lbl) + case *ast.CommClause: + if stmt == nil { + return + } + kind = dbscheme.CommClauseType.Index() + extractStmt(tw, stmt.Comm, lbl, 0) + extractStmts(tw, stmt.Body, lbl, 1, 1) + emitScopeNodeInfo(tw, stmt, lbl) + case *ast.SelectStmt: + kind = dbscheme.SelectStmtType.Index() + extractStmt(tw, stmt.Body, lbl, 0) + case *ast.ForStmt: + if stmt == nil { + return + } + kind = dbscheme.ForStmtType.Index() + extractStmt(tw, stmt.Init, lbl, 0) + extractExpr(tw, stmt.Cond, lbl, 1) + extractStmt(tw, stmt.Post, lbl, 2) + extractStmt(tw, stmt.Body, lbl, 3) + emitScopeNodeInfo(tw, stmt, lbl) + case *ast.RangeStmt: + if stmt == nil { + return + } + kind = dbscheme.RangeStmtType.Index() + extractExpr(tw, stmt.Key, lbl, 0) + extractExpr(tw, stmt.Value, lbl, 1) + extractExpr(tw, stmt.X, lbl, 2) + extractStmt(tw, stmt.Body, lbl, 3) + emitScopeNodeInfo(tw, stmt, lbl) + default: + log.Fatalf("unknown statement of type %T", stmt) + } + dbscheme.StmtsTable.Emit(tw, lbl, kind, parent, idx) + extractNodeLocation(tw, stmt, lbl) +} + +// extractStmts extracts AST information for a list of statements, which are children of +// the given parent +// `idx` is the index of the first child in the list, and `dir` is the index increment of +// each child over its preceding child (usually either 1 for assigning increasing indices, or +// -1 for decreasing indices) +func extractStmts(tw *trap.Writer, stmts []ast.Stmt, parent trap.Label, idx int, dir int) { + if stmts != nil { + for _, stmt := range stmts { + extractStmt(tw, stmt, parent, idx) + idx += dir + } + } + +} + +// extractDecl extracts AST information for the given declaration +func extractDecl(tw *trap.Writer, decl ast.Decl, parent trap.Label, idx int) { + lbl := tw.Labeler.LocalID(decl) + var kind int + switch decl := decl.(type) { + case *ast.BadDecl: + kind = dbscheme.BadDeclType.Index() + case *ast.GenDecl: + if decl == nil { + return + } + switch decl.Tok { + case token.IMPORT: + kind = dbscheme.ImportDeclType.Index() + case token.CONST: + kind = dbscheme.ConstDeclType.Index() + case token.TYPE: + kind = dbscheme.TypeDeclType.Index() + case token.VAR: + kind = dbscheme.VarDeclType.Index() + default: + log.Fatalf("unknown declaration of kind %v", decl.Tok) + } + for i, spec := range decl.Specs { + extractSpec(tw, spec, lbl, i) + } + extractDoc(tw, decl.Doc, lbl) + case *ast.FuncDecl: + if decl == nil { + return + } + kind = dbscheme.FuncDeclType.Index() + extractFields(tw, decl.Recv, lbl, -1, -1) + extractExpr(tw, decl.Name, lbl, 0) + extractExpr(tw, decl.Type, lbl, 1) + extractStmt(tw, decl.Body, lbl, 2) + extractDoc(tw, decl.Doc, lbl) + default: + log.Fatalf("unknown declaration of type %T", decl) + } + dbscheme.DeclsTable.Emit(tw, lbl, kind, parent, idx) + extractNodeLocation(tw, decl, lbl) +} + +// extractSpec extracts AST information for the given declaration specifier +func extractSpec(tw *trap.Writer, spec ast.Spec, parent trap.Label, idx int) { + lbl := tw.Labeler.LocalID(spec) + var kind int + switch spec := spec.(type) { + case *ast.ImportSpec: + if spec == nil { + return + } + kind = dbscheme.ImportSpecType.Index() + extractExpr(tw, spec.Name, lbl, 0) + extractExpr(tw, spec.Path, lbl, 1) + extractDoc(tw, spec.Doc, lbl) + case *ast.ValueSpec: + if spec == nil { + return + } + kind = dbscheme.ValueSpecType.Index() + for i, name := range spec.Names { + extractExpr(tw, name, lbl, -(1 + i)) + } + extractExpr(tw, spec.Type, lbl, 0) + extractExprs(tw, spec.Values, lbl, 1, 1) + extractDoc(tw, spec.Doc, lbl) + case *ast.TypeSpec: + if spec == nil { + return + } + kind = dbscheme.TypeSpecType.Index() + extractExpr(tw, spec.Name, lbl, 0) + extractExpr(tw, spec.Type, lbl, 1) + extractDoc(tw, spec.Doc, lbl) + } + dbscheme.SpecsTable.Emit(tw, lbl, kind, parent, idx) + extractNodeLocation(tw, spec, lbl) +} + +// extractType extracts type information for `tp` and returns its associated label; +// types are only extracted once, so the second time `extractType` is invoked it simply returns the label +func extractType(tw *trap.Writer, tp types.Type) trap.Label { + lbl, exists := getTypeLabel(tw, tp) + if !exists { + var kind int + switch tp := tp.(type) { + case *types.Basic: + branch := dbscheme.BasicTypes[tp.Kind()] + if branch == nil { + log.Fatalf("unknown basic type %v", tp.Kind()) + } + kind = branch.Index() + case *types.Array: + kind = dbscheme.ArrayType.Index() + dbscheme.ArrayLengthTable.Emit(tw, lbl, fmt.Sprintf("%d", tp.Len())) + extractElementType(tw, lbl, tp.Elem()) + case *types.Slice: + kind = dbscheme.SliceType.Index() + extractElementType(tw, lbl, tp.Elem()) + case *types.Struct: + kind = dbscheme.StructType.Index() + for i := 0; i < tp.NumFields(); i++ { + field := tp.Field(i) + + // ensure the field is associated with a label + fieldlbl, exists := tw.Labeler.FieldID(field, i, lbl) + if !exists { + extractObject(tw, field, fieldlbl) + } + + dbscheme.FieldStructsTable.Emit(tw, fieldlbl, lbl) + + name := field.Name() + if field.Embedded() { + name = "" + } + extractComponentType(tw, lbl, i, name, field.Type()) + } + case *types.Pointer: + kind = dbscheme.PointerType.Index() + extractBaseType(tw, lbl, tp.Elem()) + case *types.Interface: + kind = dbscheme.InterfaceType.Index() + for i := 0; i < tp.NumMethods(); i++ { + meth := tp.Method(i) + + // get the receiver type of component methods; for interfaces + // this can be different from the type used to get the method + recvTyp := tp.Method(i).Type().(*types.Signature).Recv().Type() + recvlbl := extractType(tw, recvTyp) // ensure receiver type has a label + // ensure the method is associated with a label + methlbl, exists := tw.Labeler.MethodID(meth, recvlbl) + if !exists { + extractObject(tw, meth, methlbl) + } + + extractComponentType(tw, lbl, i, meth.Name(), meth.Type()) + } + case *types.Tuple: + kind = dbscheme.TupleType.Index() + for i := 0; i < tp.Len(); i++ { + extractComponentType(tw, lbl, i, "", tp.At(i).Type()) + } + case *types.Signature: + kind = dbscheme.SignatureType.Index() + parms, results := tp.Params(), tp.Results() + if parms != nil { + for i := 0; i < parms.Len(); i++ { + parm := parms.At(i) + extractComponentType(tw, lbl, i+1, "", parm.Type()) + } + } + if results != nil { + for i := 0; i < results.Len(); i++ { + result := results.At(i) + extractComponentType(tw, lbl, -(i + 1), "", result.Type()) + } + } + case *types.Map: + kind = dbscheme.MapType.Index() + extractKeyType(tw, lbl, tp.Key()) + extractElementType(tw, lbl, tp.Elem()) + case *types.Chan: + kind = dbscheme.ChanTypes[tp.Dir()].Index() + extractElementType(tw, lbl, tp.Elem()) + case *types.Named: + kind = dbscheme.NamedType.Index() + dbscheme.TypeNameTable.Emit(tw, lbl, tp.Obj().Name()) + extractUnderlyingType(tw, lbl, tp.Underlying()) + + entitylbl, exists := tw.Labeler.LookupObjectID(tp.Obj(), lbl) + if entitylbl == trap.InvalidLabel { + log.Printf("Omitting type-object binding for unknown object %v.\n", tp.Obj()) + } else { + if !exists { + extractObject(tw, tp.Obj(), entitylbl) + } + dbscheme.TypeObjectTable.Emit(tw, lbl, entitylbl) + } + + // ensure all methods have labels + for i := 0; i < tp.NumMethods(); i++ { + meth := tp.Method(i) + recv := meth.Type().(*types.Signature).Recv() + typ := recv.Type() + recvTypeLbl := extractType(tw, typ) // ensure receiver type has a label + methlbl, exists := tw.Labeler.MethodID(tp.Method(i), recvTypeLbl) + if !exists { + extractObject(tw, meth, methlbl) + } + } + default: + log.Fatalf("unexpected type %T", tp) + } + dbscheme.TypesTable.Emit(tw, lbl, kind) + } + return lbl +} + +// getTypeLabel looks up the label associated with `tp`, creating a new label if +// it does not have one yet; the second result indicates whether the label +// already existed +// +// Type labels refer to global keys to ensure that if the same type is +// encountered during the extraction of different files it is still ultimately +// mapped to the same entity. In particular, this means that keys for compound +// types refer to the labels of their component types. For named types, the key +// is constructed from their globally unique ID. This prevents cyclic type keys +// since type recursion in Go always goes through named types. +func getTypeLabel(tw *trap.Writer, tp types.Type) (trap.Label, bool) { + lbl, exists := tw.Labeler.TypeLabels[tp] + if !exists { + switch tp := tp.(type) { + case *types.Basic: + lbl = tw.Labeler.GlobalID(fmt.Sprintf("%d;basictype", tp.Kind())) + case *types.Array: + len := tp.Len() + elem := extractType(tw, tp.Elem()) + lbl = tw.Labeler.GlobalID(fmt.Sprintf("%d,{%s};arraytype", len, elem)) + case *types.Slice: + elem := extractType(tw, tp.Elem()) + lbl = tw.Labeler.GlobalID(fmt.Sprintf("{%s};slicetype", elem)) + case *types.Struct: + var b strings.Builder + for i := 0; i < tp.NumFields(); i++ { + field := tp.Field(i) + fieldTypeLbl := extractType(tw, field.Type()) + if i > 0 { + b.WriteString(",") + } + name := field.Name() + if field.Embedded() { + name = "" + } + fmt.Fprintf(&b, "%s,{%s},%s", name, fieldTypeLbl, tp.Tag(i)) + } + lbl = tw.Labeler.GlobalID(fmt.Sprintf("%s;structtype", b.String())) + case *types.Pointer: + base := extractType(tw, tp.Elem()) + lbl = tw.Labeler.GlobalID(fmt.Sprintf("{%s};pointertype", base)) + case *types.Interface: + var b strings.Builder + for i := 0; i < tp.NumMethods(); i++ { + meth := tp.Method(i) + methLbl := extractType(tw, meth.Type()) + if i > 0 { + b.WriteString(",") + } + fmt.Fprintf(&b, "%s,{%s}", meth.Id(), methLbl) + } + lbl = tw.Labeler.GlobalID(fmt.Sprintf("%s;interfacetype", b.String())) + case *types.Tuple: + var b strings.Builder + for i := 0; i < tp.Len(); i++ { + compLbl := extractType(tw, tp.At(i).Type()) + if i > 0 { + b.WriteString(",") + } + fmt.Fprintf(&b, "{%s}", compLbl) + } + lbl = tw.Labeler.GlobalID(fmt.Sprintf("%s;tupletype", b.String())) + case *types.Signature: + var b strings.Builder + parms, results := tp.Params(), tp.Results() + if parms != nil { + for i := 0; i < parms.Len(); i++ { + parmLbl := extractType(tw, parms.At(i).Type()) + if i > 0 { + b.WriteString(",") + } + fmt.Fprintf(&b, "{%s}", parmLbl) + } + } + b.WriteString(";") + if results != nil { + for i := 0; i < results.Len(); i++ { + resultLbl := extractType(tw, results.At(i).Type()) + if i > 0 { + b.WriteString(",") + } + fmt.Fprintf(&b, "{%s}", resultLbl) + } + } + lbl = tw.Labeler.GlobalID(fmt.Sprintf("%s;signaturetype", b.String())) + case *types.Map: + key := extractType(tw, tp.Key()) + value := extractType(tw, tp.Elem()) + lbl = tw.Labeler.GlobalID(fmt.Sprintf("{%s},{%s};maptype", key, value)) + case *types.Chan: + dir := tp.Dir() + elem := extractType(tw, tp.Elem()) + lbl = tw.Labeler.GlobalID(fmt.Sprintf("%v,{%s};chantype", dir, elem)) + case *types.Named: + entitylbl, exists := tw.Labeler.LookupObjectID(tp.Obj(), lbl) + if entitylbl == trap.InvalidLabel { + panic(fmt.Sprintf("Cannot construct label for named type %v (underlying object is %v).\n", tp, tp.Obj())) + } + if !exists { + extractObject(tw, tp.Obj(), entitylbl) + } + lbl = tw.Labeler.GlobalID(fmt.Sprintf("{%s};namedtype", entitylbl)) + } + tw.Labeler.TypeLabels[tp] = lbl + } + return lbl, exists +} + +// extractKeyType extracts `key` as the key type of the map type `mp` +func extractKeyType(tw *trap.Writer, mp trap.Label, key types.Type) { + dbscheme.KeyTypeTable.Emit(tw, mp, extractType(tw, key)) +} + +// extractElementType extracts `element` as the element type of the container type `container` +func extractElementType(tw *trap.Writer, container trap.Label, element types.Type) { + dbscheme.ElementTypeTable.Emit(tw, container, extractType(tw, element)) +} + +// extractBaseType extracts `base` as the base type of the pointer type `ptr` +func extractBaseType(tw *trap.Writer, ptr trap.Label, base types.Type) { + dbscheme.BaseTypeTable.Emit(tw, ptr, extractType(tw, base)) +} + +// extractUnderlyingType extracts `underlying` as the underlying type of the +// named type `named` +func extractUnderlyingType(tw *trap.Writer, named trap.Label, underlying types.Type) { + dbscheme.UnderlyingTypeTable.Emit(tw, named, extractType(tw, underlying)) +} + +// extractComponentType extracts `component` as the `idx`th component type of `parent` with name `name` +func extractComponentType(tw *trap.Writer, parent trap.Label, idx int, name string, component types.Type) { + dbscheme.ComponentTypesTable.Emit(tw, parent, idx, name, extractType(tw, component)) +} + +// extractNumLines extracts lines-of-code and lines-of-comments information for the +// given file +func extractNumLines(tw *trap.Writer, fileName string, ast *ast.File) { + f := tw.Package.Fset.File(ast.Pos()) + + lineCount := f.LineCount() + + // count lines of code by tokenizing + linesOfCode := 0 + src, err := ioutil.ReadFile(fileName) + if err != nil { + log.Fatalf("Unable to read file %s.", fileName) + } + var s scanner.Scanner + lastCodeLine := -1 + s.Init(f, src, nil, 0) + for { + pos, tok, lit := s.Scan() + if tok == token.EOF { + break + } else if tok != token.ILLEGAL { + tkStartLine := f.Position(pos).Line + tkEndLine := tkStartLine + strings.Count(lit, "\n") + if tkEndLine > lastCodeLine { + linesOfCode += tkEndLine - tkStartLine + 1 + lastCodeLine = tkEndLine + } + } + } + + // count lines of comments by iterating over ast.Comments + linesOfComments := 0 + for _, cg := range ast.Comments { + for _, g := range cg.List { + fset := tw.Package.Fset + startPos, endPos := fset.Position(g.Pos()), fset.Position(g.End()) + linesOfComments += endPos.Line - startPos.Line + 1 + } + } + + dbscheme.NumlinesTable.Emit(tw, tw.Labeler.FileLabel(), lineCount, linesOfCode, linesOfComments) +} diff --git a/extractor/net/sourceforge/pmd/cpd/AbstractLanguage.java b/extractor/net/sourceforge/pmd/cpd/AbstractLanguage.java new file mode 100644 index 00000000..363be4e8 --- /dev/null +++ b/extractor/net/sourceforge/pmd/cpd/AbstractLanguage.java @@ -0,0 +1,13 @@ +package net.sourceforge.pmd.cpd; + +/* + * This is a stub definition for pmd's AbstractLanguage class + * including only the API used by the GoLanguage class. + */ + +public abstract class AbstractLanguage { + + public AbstractLanguage(String... extensions) {} + + public abstract Tokenizer getTokenizer(boolean fuzzyMatch); +} diff --git a/extractor/net/sourceforge/pmd/cpd/GoLanguage.java b/extractor/net/sourceforge/pmd/cpd/GoLanguage.java new file mode 100644 index 00000000..400a52c0 --- /dev/null +++ b/extractor/net/sourceforge/pmd/cpd/GoLanguage.java @@ -0,0 +1,68 @@ +package net.sourceforge.pmd.cpd; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.ProcessBuilder.Redirect; +import java.nio.charset.Charset; +import java.nio.file.Paths; +import java.util.List; +import opencsv.CSVReader; + +public class GoLanguage extends AbstractLanguage { + public GoLanguage() { + super(".go"); + } + + @Override + public Tokenizer getTokenizer(final boolean fuzzyMatch) { + return new Tokenizer() { + @Override + public void tokenize(SourceCode tokens, List tokenEntries) { + String fileName = tokens.getFileName(); + String platform = "linux", exe = ""; + + String osName = System.getProperty("os.name", "unknown"); + if (osName.contains("Windows")) { + platform = "win"; + exe = ".exe"; + } else if (osName.contains("Mac OS X")) { + platform = "osx"; + } + + // get tools folder from SEMMLE_DIST + String toolsDir = null; + String dist = System.getenv("SEMMLE_DIST"); + if (dist != null && !dist.isEmpty()) { + toolsDir = dist + "/language-packs/go/tools/platform/" + platform; + } + + String goTokenizer = toolsDir == null ? "go-tokenizer" : toolsDir + "/bin/go-tokenizer"; + goTokenizer += exe; + ProcessBuilder pb = new ProcessBuilder(Paths.get(goTokenizer).toString(), fileName); + pb.redirectError(Redirect.INHERIT); + try { + Process process = pb.start(); + try ( + CSVReader r = new CSVReader(new InputStreamReader(process.getInputStream(), Charset.forName("UTF-8"))) + ) { + String[] row; + while ((row = r.readNext()) != null) { + String text = row[0]; + String fuzzyText = row[1]; + int beginLine = Integer.parseInt(row[2]); + int beginColumn = Integer.parseInt(row[3]); + int endLine = Integer.parseInt(row[4]); + int endColumn = Integer.parseInt(row[5]); + tokenEntries.add(new TokenEntry(fuzzyMatch ? text : fuzzyText, fileName, beginLine, beginColumn, endLine, endColumn)); + } + } + int exitCode = process.waitFor(); + if (exitCode != 0) + throw new RuntimeException("Tokenizing " + fileName + " returned " + exitCode + "."); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + }; + } +} diff --git a/extractor/net/sourceforge/pmd/cpd/SourceCode.java b/extractor/net/sourceforge/pmd/cpd/SourceCode.java new file mode 100644 index 00000000..f3ad49f8 --- /dev/null +++ b/extractor/net/sourceforge/pmd/cpd/SourceCode.java @@ -0,0 +1,12 @@ +package net.sourceforge.pmd.cpd; + +/* + * This is a stub definition for pmd's SourceCode class + * including only the API used by the GoLanguage class. + */ + +public class SourceCode { + public String getFileName() { + return null; + } +} diff --git a/extractor/net/sourceforge/pmd/cpd/TokenEntry.java b/extractor/net/sourceforge/pmd/cpd/TokenEntry.java new file mode 100644 index 00000000..d599ebea --- /dev/null +++ b/extractor/net/sourceforge/pmd/cpd/TokenEntry.java @@ -0,0 +1,11 @@ +package net.sourceforge.pmd.cpd; + +/* + * This is a stub definition for pmd's TokenEntry class + * including only the API used by the GoLanguage class. + */ + +public class TokenEntry { + public TokenEntry(String image, String tokenSrcID, int beginLine, int beginColumn, int endLine, int endColumn) { + } +} diff --git a/extractor/net/sourceforge/pmd/cpd/Tokenizer.java b/extractor/net/sourceforge/pmd/cpd/Tokenizer.java new file mode 100644 index 00000000..cc94bbbb --- /dev/null +++ b/extractor/net/sourceforge/pmd/cpd/Tokenizer.java @@ -0,0 +1,12 @@ +package net.sourceforge.pmd.cpd; + +/* + * This is a stub definition for pmd's Tokenizer interface + * including only the API used by the GoLanguage class. + */ + +import java.util.List; + +public interface Tokenizer { + void tokenize(SourceCode tokens, List tokenEntries); +} diff --git a/extractor/opencsv/CSVParser.java b/extractor/opencsv/CSVParser.java new file mode 100644 index 00000000..e3a864be --- /dev/null +++ b/extractor/opencsv/CSVParser.java @@ -0,0 +1,207 @@ +/** + Copyright 2005 Bytecode Pty Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package opencsv; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * A very simple CSV parser released under a commercial-friendly license. + * This just implements splitting a single line into fields. + * + * @author Glen Smith + * @author Rainer Pruy + * + */ +public class CSVParser { + + private final char separator; + + private final char quotechar; + + private final char escape; + + private final boolean strictQuotes; + + private StringBuilder buf = new StringBuilder(INITIAL_READ_SIZE); + + /** The default separator to use if none is supplied to the constructor. */ + public static final char DEFAULT_SEPARATOR = ','; + + private static final int INITIAL_READ_SIZE = 128; + + /** + * The default quote character to use if none is supplied to the + * constructor. + */ + public static final char DEFAULT_QUOTE_CHARACTER = '"'; + + + /** + * The default escape character to use if none is supplied to the + * constructor. + */ + public static final char DEFAULT_ESCAPE_CHARACTER = '"'; + + /** + * The default strict quote behavior to use if none is supplied to the + * constructor + */ + public static final boolean DEFAULT_STRICT_QUOTES = false; + + /** + * Constructs CSVReader with supplied separator and quote char. + * Allows setting the "strict quotes" flag + * @param separator + * the delimiter to use for separating entries + * @param quotechar + * the character to use for quoted elements + * @param escape + * the character to use for escaping a separator or quote + * @param strictQuotes + * if true, characters outside the quotes are ignored + */ + CSVParser(char separator, char quotechar, char escape, boolean strictQuotes) { + this.separator = separator; + this.quotechar = quotechar; + this.escape = escape; + this.strictQuotes = strictQuotes; + } + + /** + * + * @return true if something was left over from last call(s) + */ + public boolean isPending() { + return buf.length() != 0; + } + + public String[] parseLineMulti(String nextLine) throws IOException { + return parseLine(nextLine, true); + } + + public String[] parseLine(String nextLine) throws IOException { + return parseLine(nextLine, false); + } + /** + * Parses an incoming String and returns an array of elements. + * + * @param nextLine + * the string to parse + * @return the comma-tokenized list of elements, or null if nextLine is null + * @throws IOException if bad things happen during the read + */ + private String[] parseLine(String nextLine, boolean multi) throws IOException { + + if (!multi && isPending()) { + clear(); + } + + if (nextLine == null) { + if (isPending()) { + String s = buf.toString(); + clear(); + return new String[] {s}; + } else { + return null; + } + } + + ListtokensOnThisLine = new ArrayList(); + boolean inQuotes = isPending(); + for (int i = 0; i < nextLine.length(); i++) { + + char c = nextLine.charAt(i); + if (c == this.escape && isNextCharacterEscapable(nextLine, inQuotes, i)) { + buf.append(nextLine.charAt(i+1)); + i++; + } else if (c == quotechar) { + if( isNextCharacterEscapedQuote(nextLine, inQuotes, i) ){ + buf.append(nextLine.charAt(i+1)); + i++; + }else{ + inQuotes = !inQuotes; + // the tricky case of an embedded quote in the middle: a,bc"d"ef,g + if (!strictQuotes) { + if(i>2 //not on the beginning of the line + && nextLine.charAt(i-1) != this.separator //not at the beginning of an escape sequence + && nextLine.length()>(i+1) && + nextLine.charAt(i+1) != this.separator //not at the end of an escape sequence + ){ + buf.append(c); + } + } + } + } else if (c == separator && !inQuotes) { + tokensOnThisLine.add(buf.toString()); + clear(); // start work on next token + } else { + if (!strictQuotes || inQuotes) + buf.append(c); + } + } + // line is done - check status + if (inQuotes) { + if (multi) { + // continuing a quoted section, re-append newline + buf.append('\n'); + // this partial content is not to be added to field list yet + } else { + throw new IOException("Un-terminated quoted field at end of CSV line"); + } + } else { + tokensOnThisLine.add(buf.toString()); + clear(); + } + return tokensOnThisLine.toArray(new String[tokensOnThisLine.size()]); + + } + + /** + * precondition: the current character is a quote or an escape + * @param nextLine the current line + * @param inQuotes true if the current context is quoted + * @param i current index in line + * @return true if the following character is a quote + */ + private boolean isNextCharacterEscapedQuote(String nextLine, boolean inQuotes, int i) { + return inQuotes // we are in quotes, therefore there can be escaped quotes in here. + && nextLine.length() > (i+1) // there is indeed another character to check. + && nextLine.charAt(i+1) == quotechar; + } + + /** + * precondition: the current character is an escape + * @param nextLine the current line + * @param inQuotes true if the current context is quoted + * @param i current index in line + * @return true if the following character is a quote + */ + protected boolean isNextCharacterEscapable(String nextLine, boolean inQuotes, int i) { + return inQuotes // we are in quotes, therefore there can be escaped quotes in here. + && nextLine.length() > (i+1) // there is indeed another character to check. + && ( nextLine.charAt(i+1) == quotechar || nextLine.charAt(i+1) == this.escape); + } + + /** + * Reset the buffer used for storing the current field's value + */ + private void clear() { + buf.setLength(0); + } +} diff --git a/extractor/opencsv/CSVReader.java b/extractor/opencsv/CSVReader.java new file mode 100644 index 00000000..11b537e4 --- /dev/null +++ b/extractor/opencsv/CSVReader.java @@ -0,0 +1,192 @@ +/** + Copyright 2005 Bytecode Pty Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package opencsv; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +/** + * A very simple CSV reader released under a commercial-friendly license. + * + * @author Glen Smith + * + */ +public class CSVReader implements Closeable { + + private final BufferedReader br; + + private boolean hasNext = true; + + private final CSVParser parser; + + private final int skipLines; + + private boolean linesSkipped; + + /** The line number of the last physical line read (one-based). */ + private int curline = 0; + + /** The physical line number at which the last logical line read started (one-based). */ + private int startLine = 0; + + /** + * The default line to start reading. + */ + private static final int DEFAULT_SKIP_LINES = 0; + + /** + * Constructs CSVReader using a comma for the separator. + * + * @param reader + * the reader to an underlying CSV source. + */ + public CSVReader(Reader reader) { + this(reader, + CSVParser.DEFAULT_SEPARATOR, CSVParser.DEFAULT_QUOTE_CHARACTER, + CSVParser.DEFAULT_ESCAPE_CHARACTER, DEFAULT_SKIP_LINES, + CSVParser.DEFAULT_STRICT_QUOTES); + } + + /** + * Constructs CSVReader with supplied separator and quote char. + * + * @param reader + * the reader to an underlying CSV source. + * @param separator + * the delimiter to use for separating entries + * @param quotechar + * the character to use for quoted elements + * @param escape + * the character to use for escaping a separator or quote + * @param line + * the line number to skip for start reading + * @param strictQuotes + * sets if characters outside the quotes are ignored + */ + private CSVReader(Reader reader, char separator, char quotechar, char escape, int line, boolean strictQuotes) { + this.br = new BufferedReader(reader); + this.parser = new CSVParser(separator, quotechar, escape, strictQuotes); + this.skipLines = line; + } + + + /** + * Reads the entire file into a List with each element being a String[] of + * tokens. + * + * @return a List of String[], with each String[] representing a line of the + * file. + * + * @throws IOException + * if bad things happen during the read + */ + public List readAll() throws IOException { + + List allElements = new ArrayList(); + while (hasNext) { + String[] nextLineAsTokens = readNext(); + if (nextLineAsTokens != null) + allElements.add(nextLineAsTokens); + } + return allElements; + + } + + /** + * Reads the next line from the buffer and converts to a string array. + * + * @return a string array with each comma-separated element as a separate + * entry, or null if there are no more lines to read. + * + * @throws IOException + * if bad things happen during the read + */ + public String[] readNext() throws IOException { + boolean first = true; + String[] result = null; + do { + String nextLine = getNextLine(); + + if (first) { + startLine = curline; + first = false; + } + + if (!hasNext) { + return result; // should throw if still pending? + } + String[] r = parser.parseLineMulti(nextLine); + if (r.length > 0) { + if (result == null) { + result = r; + } else { + String[] t = new String[result.length+r.length]; + System.arraycopy(result, 0, t, 0, result.length); + System.arraycopy(r, 0, t, result.length, r.length); + result = t; + } + } + } while (parser.isPending()); + return result; + } + + /** + * Reads the next line from the file. + * + * @return the next line from the file without trailing newline + * @throws IOException + * if bad things happen during the read + */ + private String getNextLine() throws IOException { + if (!this.linesSkipped) { + for (int i = 0; i < skipLines; i++) { + br.readLine(); + ++curline; + } + this.linesSkipped = true; + } + String nextLine = br.readLine(); + if (nextLine == null) { + hasNext = false; + } else { + ++curline; + } + return hasNext ? nextLine : null; + } + + /** + * Closes the underlying reader. + * + * @throws IOException if the close fails + */ + @Override + public void close() throws IOException{ + br.close(); + } + + /** + * Return the physical line number (one-based) at which the last logical line read started, + * or zero if no line has been read yet. + */ + public int getStartLine() { + return startLine; + } +} diff --git a/extractor/semaphore.go b/extractor/semaphore.go new file mode 100644 index 00000000..127f94aa --- /dev/null +++ b/extractor/semaphore.go @@ -0,0 +1,42 @@ +package extractor + +import ( + "log" +) + +type Unit struct{} + +var unit = Unit{} + +type semaphore struct { + counter, lock chan Unit +} + +func (s *semaphore) acquire(n int) { + if s != nil { + if cap(s.counter) < n { + log.Fatalf("Tried to acquire more resources than were available.") + } + s.lock <- unit + for i := 0; i < n; i++ { + s.counter <- unit + } + <-s.lock + } +} + +func (s *semaphore) release(n int) { + if s != nil { + for i := 0; i < n; i++ { + <-s.counter + } + } +} + +func newSemaphore(max int) *semaphore { + if max > 0 { + return &semaphore{make(chan Unit, max), make(chan Unit, 1)} + } else { + return nil + } +} diff --git a/extractor/srcarchive/projectlayout.go b/extractor/srcarchive/projectlayout.go new file mode 100644 index 00000000..da717dd4 --- /dev/null +++ b/extractor/srcarchive/projectlayout.go @@ -0,0 +1,105 @@ +package srcarchive + +import ( + "bufio" + "errors" + "fmt" + "os" + "strings" +) + +// ProjectLayout describes a very simple project layout rewriting paths starting +// with `from` to start with `to` instead. +// +// We currently only support project layouts of the form +// +// # to +// from// +type ProjectLayout struct { + from, to string +} + +// normaliseSlashes adds an initial slash to `path` if there isn't one, and trims +// a final slash if there is one +func normaliseSlashes(path string) string { + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + return strings.TrimSuffix(path, "/") +} + +// LoadProjectLayout loads a project layout from the given file, returning an error +// if the file does not have the right format +func LoadProjectLayout(file *os.File) (*ProjectLayout, error) { + res := ProjectLayout{} + scanner := bufio.NewScanner(file) + + line := "" + for ; line == "" && scanner.Scan(); line = strings.TrimSpace(scanner.Text()) { + } + + if !strings.HasPrefix(line, "#") { + return nil, fmt.Errorf("first line of project layout should start with #, but got %s", line) + } + res.to = normaliseSlashes(strings.TrimSpace(strings.TrimPrefix(line, "#"))) + + if !scanner.Scan() { + return nil, errors.New("empty section in project-layout file") + } + + line = strings.TrimSpace(scanner.Text()) + + if !strings.HasSuffix(line, "//") { + return nil, errors.New("unsupported project-layout feature") + } + line = strings.TrimSuffix(line, "//") + + if strings.HasPrefix(line, "-") || strings.Contains(line, "*") || strings.Contains(line, "//") { + return nil, errors.New("unsupported project-layout feature") + } + res.from = normaliseSlashes(line) + + for scanner.Scan() { + if strings.TrimSpace(scanner.Text()) != "" { + return nil, errors.New("only one section with one rewrite supported") + } + } + + return &res, nil +} + +// transformString transforms `str` as specified by the project layout: if it starts with the `from` +// prefix, that prefix is relaced by `to`; otherwise the string is returned unchanged +func (p *ProjectLayout) transformString(str string) string { + if str == p.from { + return p.to + } + if strings.HasPrefix(str, p.from+"/") { + return p.to + "/" + str[len(p.from)+1:] + } + return str +} + +// isWindowsPath checks whether the substring of `path` starting at `idx` looks like a (slashified) +// Windows path, that is, starts with a drive letter followed by a colon and a slash +func isWindowsPath(path string, idx int) bool { + return len(path) >= 3+idx && + path[idx] != '/' && + path[idx+1] == ':' && path[idx+2] == '/' +} + +// Transform transforms the given path according to the project layout: if it starts with the `from` +// prefix, that prefix is relaced by `to`; otherwise the path is returned unchanged. +// +// Unlike the (internal) method `transformString`, this method handles Windows paths sensibly. +func (p *ProjectLayout) Transform(path string) string { + if isWindowsPath(path, 0) { + result := p.transformString("/" + path) + if isWindowsPath(result, 1) && result[0] == '/' { + return result[1:] + } + return result + } else { + return p.transformString(path) + } +} diff --git a/extractor/srcarchive/projectlayout_test.go b/extractor/srcarchive/projectlayout_test.go new file mode 100644 index 00000000..fb9f180f --- /dev/null +++ b/extractor/srcarchive/projectlayout_test.go @@ -0,0 +1,136 @@ +package srcarchive + +import ( + "io/ioutil" + "os" + "testing" +) + +func mkProjectLayout(projectLayoutSource string, t *testing.T) (*ProjectLayout, error) { + pt, err := ioutil.TempFile("", "path-transformer") + if err != nil { + t.Fatalf("Unable to create temporary file for project layout: %s", err.Error()) + } + defer os.Remove(pt.Name()) + _, err = pt.WriteString(projectLayoutSource) + if err != nil { + t.Fatalf("Unable to write to temporary file for project layout: %s", err.Error()) + } + err = pt.Close() + if err != nil { + t.Fatalf("Unable to close path transformer file: %s.", err.Error()) + } + + pt, err = os.Open(pt.Name()) + if err != nil { + t.Fatalf("Unable to open path transformer file: %s.", err.Error()) + } + + return LoadProjectLayout(pt) +} + +func testTransformation(projectLayout *ProjectLayout, t *testing.T, path string, expected string) { + actual := projectLayout.Transform(path) + if actual != expected { + t.Errorf("Expected %s to be transformed to %s, but got %s", path, expected, actual) + } +} + +func TestValidProjectLayout(t *testing.T) { + p, err := mkProjectLayout(` +# /opt/src +/opt/src/root/src/org/repo// +`, t) + + if err != nil { + t.Fatalf("Error loading project layout: %s", err.Error()) + } + + testTransformation(p, t, "/opt/src/root/src/org/repo", "/opt/src") + testTransformation(p, t, "/opt/src/root/src/org/repo/", "/opt/src/") + testTransformation(p, t, "/opt/src/root/src/org/repo/main.go", "/opt/src/main.go") + testTransformation(p, t, "/opt/not/in/src", "/opt/not/in/src") + testTransformation(p, t, "/opt/src/root/srcorg/repo", "/opt/src/root/srcorg/repo") + testTransformation(p, t, "opt/src/root/src/org/repo", "opt/src/root/src/org/repo") +} + +func TestWindowsPaths(t *testing.T) { + p, err := mkProjectLayout(` +# /c:/virtual +/d:// +`, t) + + if err != nil { + t.Fatalf("Error loading project layout: %s", err.Error()) + } + + testTransformation(p, t, "d:/foo", "c:/virtual/foo") +} + +func TestWindowsToUnixPaths(t *testing.T) { + p, err := mkProjectLayout(` +# /opt/src +/d:// +`, t) + + if err != nil { + t.Fatalf("Error loading project layout: %s", err.Error()) + } + + testTransformation(p, t, "d:/foo", "/opt/src/foo") +} + +func TestEmptyProjectLayout(t *testing.T) { + _, err := mkProjectLayout("", t) + if err == nil { + t.Error("Expected error on empty project layout") + } +} + +func TestEmptyProjectLayout2(t *testing.T) { + _, err := mkProjectLayout(` + `, t) + if err == nil { + t.Error("Expected error on empty project layout") + } +} + +func TestExclusion(t *testing.T) { + _, err := mkProjectLayout(` +# /opt/src +-/foo// +`, t) + if err == nil { + t.Error("Expected error on exclusion") + } +} + +func TestStar(t *testing.T) { + _, err := mkProjectLayout(` +# /opt/src +/foo/**/bar// +`, t) + if err == nil { + t.Error("Expected error on star") + } +} + +func TestDoubleSlash(t *testing.T) { + _, err := mkProjectLayout(` +# /opt/src +/foo//bar// +`, t) + if err == nil { + t.Error("Expected error on multiple double slashes") + } +} + +func TestInternalDoubleSlash(t *testing.T) { + _, err := mkProjectLayout(` +# /opt/src +/foo//bar +`, t) + if err == nil { + t.Error("Expected error on internal double slash") + } +} diff --git a/extractor/srcarchive/srcarchive.go b/extractor/srcarchive/srcarchive.go new file mode 100644 index 00000000..84eef29d --- /dev/null +++ b/extractor/srcarchive/srcarchive.go @@ -0,0 +1,82 @@ +package srcarchive + +import ( + "errors" + "io" + "log" + "os" + "path/filepath" + "strings" +) + +var pathTransformer *ProjectLayout + +func init() { + pt := os.Getenv("SEMMLE_PATH_TRANSFORMER") + if pt != "" { + ptf, err := os.Open(pt) + if err != nil { + log.Fatalf("Unable to open path transformer %s: %s.\n", pt, err.Error()) + } + pathTransformer, err = LoadProjectLayout(ptf) + if err != nil { + log.Fatalf("Unable to initialize path transformer: %s.\n", err.Error()) + } + } +} + +// Add inserts the file with the given `path` into the source archive, returning a non-nil +// error value if it fails +func Add(path string) error { + srcArchive, err := srcArchive() + if err != nil { + return err + } + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + archiveFilePath := filepath.Join(srcArchive, AppendablePath(path)) + err = os.MkdirAll(filepath.Dir(archiveFilePath), 0755) + if err != nil { + return err + } + archiveFile, err := os.Create(archiveFilePath) + if err != nil { + return err + } + defer archiveFile.Close() + + _, err = io.Copy(archiveFile, file) + return err +} + +func srcArchive() (string, error) { + srcArchive := os.Getenv("SOURCE_ARCHIVE") + if srcArchive == "" { + return "", errors.New("environment variable SOURCE_ARCHIVE not set") + } + err := os.MkdirAll(srcArchive, 0755) + if err != nil { + return "", err + } + return srcArchive, nil +} + +// TransformPath applies the transformations specified by `SEMMLE_PATH_TRANSFORMER` (if any) to the +// given path +func TransformPath(path string) string { + if pathTransformer != nil { + return filepath.FromSlash(pathTransformer.Transform(filepath.ToSlash(path))) + } + return path +} + +// AppendablePath transforms the given path and also replaces colons with underscores to make it +// possible to append it to a base path on Windows +func AppendablePath(path string) string { + return strings.ReplaceAll(TransformPath(path), ":", "_") +} diff --git a/extractor/trap/labels.go b/extractor/trap/labels.go new file mode 100644 index 00000000..3ebe9c35 --- /dev/null +++ b/extractor/trap/labels.go @@ -0,0 +1,216 @@ +package trap + +import ( + "fmt" + "go/ast" + "go/types" +) + +// Label represents a label +type Label struct { + id string +} + +// InvalidLabel represents an uninitialized or otherwise invalid label +var InvalidLabel Label + +func (lbl *Label) String() string { + return lbl.id +} + +// Labeler is used to represent labels for a file. It is used to write +// associate objects with labels. +type Labeler struct { + tw *Writer + + nextid int + fileLabel Label + nodeLabels map[ast.Node]Label // labels associated with AST nodes + scopeLabels map[*types.Scope]Label // labels associated with scopes + objectLabels map[types.Object]Label // labels associated with objects (that is, declared entities) + TypeLabels map[types.Type]Label // labels associated with types + keyLabels map[string]Label +} + +func newLabeler(tw *Writer) *Labeler { + return &Labeler{ + tw, + 10000, + InvalidLabel, + make(map[ast.Node]Label), + make(map[*types.Scope]Label), + make(map[types.Object]Label), + make(map[types.Type]Label), + make(map[string]Label), + } +} + +func (l *Labeler) nextID() string { + var id = l.nextid + l.nextid++ + return fmt.Sprintf("#%d", id) +} + +// GlobalID associates a label with the given `key` and returns it +func (l *Labeler) GlobalID(key string) Label { + label, exists := l.keyLabels[key] + if !exists { + id := l.nextID() + fmt.Fprintf(l.tw.w, "%s=@\"%s\"\n", id, escapeString(key)) + label = Label{id} + l.keyLabels[key] = label + } + return label +} + +// FileLabel returns the label for the file with which the trap writer is associated +func (l *Labeler) FileLabel() Label { + if l.fileLabel == InvalidLabel { + l.fileLabel = l.GlobalID(l.tw.path + ";sourcefile") + } + return l.fileLabel +} + +// LocalID associates a label with the given AST node `nd` and returns it +func (l *Labeler) LocalID(nd ast.Node) Label { + label, exists := l.nodeLabels[nd] + if !exists { + label = l.FreshID() + l.nodeLabels[nd] = label + } + return label +} + +// FreshID creates a fresh label and returns it +func (l *Labeler) FreshID() Label { + id := l.nextID() + fmt.Fprintf(l.tw.w, "%s=*\n", id) + return Label{id} +} + +// ScopeID associates a label with the given scope and returns it +func (l *Labeler) ScopeID(scope *types.Scope, pkg *types.Package) Label { + label, exists := l.scopeLabels[scope] + if !exists { + if scope == types.Universe { + label = l.GlobalID("universe;scope") + } else { + if pkg != nil && pkg.Scope() == scope { + // if this scope is the package scope + pkgLabel := l.GlobalID(pkg.Path() + ";package") + label = l.GlobalID("{" + pkgLabel.String() + "};scope") + } else { + label = l.FreshID() + } + } + l.scopeLabels[scope] = label + } + return label +} + +// LookupObjectID looks up the label associated with the given object and returns it; if the object does not have +// a label yet, it tries to construct one based on its scope and/or name, and otherwise returns InvalidLabel +func (l *Labeler) LookupObjectID(object types.Object, typelbl Label) (Label, bool) { + label, exists := l.objectLabels[object] + if !exists { + if object.Parent() == nil { + // blank identifiers and the pseudo-package `.` (from `import . "..."` imports) can only be referenced + // once, so we can use a fresh label for them + if object.Name() == "_" || object.Name() == "." { + label = l.FreshID() + l.objectLabels[object] = label + return label, false + } + label = InvalidLabel + } else { + label, exists = l.ScopedObjectID(object, typelbl) + } + } + return label, exists +} + +// ScopedObjectID associates a label with the given object and returns it, +// together with a flag indicating whether the object already had a label +// associated with it; the object must have a scope, since the scope's label is +// used to construct the label of the object. +// +// There is a special case for variables that are method receivers. When this is +// detected, we must construct a special label, as the variable can be reached +// from several files via the method. As the type label is required to construct +// the receiver object id, it is also required here. +func (l *Labeler) ScopedObjectID(object types.Object, typelbl Label) (Label, bool) { + label, exists := l.objectLabels[object] + if !exists { + scope := object.Parent() + if scope == nil { + panic(fmt.Sprintf("Object has no scope: %v :: %v.\n", object, + l.tw.Package.Fset.Position(object.Pos()))) + } else { + // associate method receiver objects to special keys, because those can be + // referenced from other files via their method + isRecv := false + if namedType, ok := object.Type().(*types.Named); ok { + for i := 0; i < namedType.NumMethods(); i++ { + meth := namedType.Method(i) + if object == meth.Type().(*types.Signature).Recv() { + isRecv = true + methlbl, _ := l.MethodID(meth, typelbl) + label, _ = l.ReceiverObjectID(object, methlbl) + } + } + } + + if !isRecv { + scopeLbl := l.ScopeID(scope, object.Pkg()) + label = l.GlobalID(fmt.Sprintf("{%s},%s;object", scopeLbl.String(), object.Name())) + } + } + l.objectLabels[object] = label + } + return label, exists +} + +// ReceiverObjectID associates a label with the given object and returns it, together with a flag indicating whether +// the object already had a label associated with it; the object must be the receiver of `methlbl`, since that label +// is used to construct the label of the object +func (l *Labeler) ReceiverObjectID(object types.Object, methlbl Label) (Label, bool) { + label, exists := l.objectLabels[object] + if !exists { + // if we can't, construct a special label + label = l.GlobalID(fmt.Sprintf("{%s},%s;receiver", methlbl.String(), object.Name())) + l.objectLabels[object] = label + } + return label, exists +} + +// FieldID associates a label with the given field and returns it, together with +// a flag indicating whether the field already had a label associated with it; +// the field must belong to `structlbl`, since that label is used to construct +// the label of the field. When the field name is the blank identifier `_`, +// `idx` is used to generate a unique name. +func (l *Labeler) FieldID(field *types.Var, idx int, structlbl Label) (Label, bool) { + label, exists := l.objectLabels[field] + if !exists { + name := field.Name() + // there can be multiple fields with the blank identifier, so use index to + // distinguish them + if field.Name() == "_" { + name = fmt.Sprintf("_%d", idx) + } + label = l.GlobalID(fmt.Sprintf("{%s},%s;field", structlbl.String(), name)) + l.objectLabels[field] = label + } + return label, exists +} + +// MethodID associates a label with the given method and returns it, together with a flag indicating whether +// the method already had a label associated with it; the method must belong to `recvlbl`, since that label +// is used to construct the label of the method +func (l *Labeler) MethodID(method types.Object, recvlbl Label) (Label, bool) { + label, exists := l.objectLabels[method] + if !exists { + label = l.GlobalID(fmt.Sprintf("{%s},%s;method", recvlbl, method.Name())) + l.objectLabels[method] = label + } + return label, exists +} diff --git a/extractor/trap/trapwriter.go b/extractor/trap/trapwriter.go new file mode 100644 index 00000000..0d8ca7eb --- /dev/null +++ b/extractor/trap/trapwriter.go @@ -0,0 +1,106 @@ +package trap + +import ( + "errors" + "fmt" + "go/types" + "io/ioutil" + "os" + "path/filepath" + + "github.com/Semmle/go/extractor/srcarchive" + "golang.org/x/tools/go/packages" +) + +// A Writer provides methods for writing data to a TRAP file +type Writer struct { + w *os.File + Labeler *Labeler + path string + trapFilePath string + Package *packages.Package +} + +// NewWriter creates a TRAP file for the given path and returns a writer for +// writing to it +func NewWriter(path string, pkg *packages.Package) (*Writer, error) { + trapFolder, err := trapFolder() + if err != nil { + return nil, err + } + trapFilePath := filepath.Join(trapFolder, srcarchive.AppendablePath(path)+".trap") + trapFileDir := filepath.Dir(trapFilePath) + err = os.MkdirAll(trapFileDir, 0755) + if err != nil { + return nil, err + } + tmpFile, err := ioutil.TempFile(trapFileDir, filepath.Base(trapFilePath)) + if err != nil { + return nil, err + } + tw := &Writer{ + tmpFile, + nil, + path, + trapFilePath, + pkg, + } + tw.Labeler = newLabeler(tw) + return tw, nil +} + +func trapFolder() (string, error) { + trapFolder := os.Getenv("TRAP_FOLDER") + if trapFolder == "" { + return "", errors.New("environment variable TRAP_FOLDER not set") + } + err := os.MkdirAll(trapFolder, 0755) + if err != nil { + return "", err + } + return trapFolder, nil +} + +// Close the underlying file writer +func (tw *Writer) Close() error { + err := tw.w.Sync() + if err != nil { + return err + } + err = tw.w.Close() + if err != nil { + return err + } + return os.Rename(tw.w.Name(), tw.trapFilePath) +} + +// ForEachObject iterates over all objects labeled by this labeler, and invokes +// the provided callback with a writer for the trap file, the object, and its +// label. +func (tw *Writer) ForEachObject(cb func(*Writer, types.Object, Label)) { + for object, lbl := range tw.Labeler.objectLabels { + cb(tw, object, lbl) + } +} + +// Emit writes out a tuple of values for the given `table` +func (tw *Writer) Emit(table string, values []interface{}) error { + fmt.Fprintf(tw.w, "%s(", table) + for i, value := range values { + if i > 0 { + fmt.Fprint(tw.w, ", ") + } + switch value := value.(type) { + case Label: + fmt.Fprint(tw.w, value.id) + case string: + fmt.Fprintf(tw.w, "\"%s\"", escapeString(value)) + case int: + fmt.Fprintf(tw.w, "%d", value) + default: + return errors.New("Cannot emit value") + } + } + fmt.Fprintf(tw.w, ")\n") + return nil +} diff --git a/extractor/trap/util.go b/extractor/trap/util.go new file mode 100644 index 00000000..2e32cfc0 --- /dev/null +++ b/extractor/trap/util.go @@ -0,0 +1,9 @@ +package trap + +import ( + "strings" +) + +func escapeString(s string) string { + return strings.Replace(s, "\"", "\"\"", -1) +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..aa3e198d --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/Semmle/go + +go 1.13 + +require golang.org/x/tools v0.0.0-20191030225452-7871c2d76733 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..46256501 --- /dev/null +++ b/go.sum @@ -0,0 +1,9 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191030225452-7871c2d76733 h1:wtYExk7epHk5WDdLiCO92FIXY5eiMtZqV1RMSLiR/3M= +golang.org/x/tools v0.0.0-20191030225452-7871c2d76733/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/ql/config/legacy-support/qlpack.yml b/ql/config/legacy-support/qlpack.yml new file mode 100644 index 00000000..cff1d02b --- /dev/null +++ b/ql/config/legacy-support/qlpack.yml @@ -0,0 +1,3 @@ +name: legacy-libraries-go +version: 0.0.0 +libraryPathDependencies: codeql-go diff --git a/ql/config/suites/lgtm/go-alerts-lgtm b/ql/config/suites/lgtm/go-alerts-lgtm new file mode 100644 index 00000000..17b8ee5c --- /dev/null +++ b/ql/config/suites/lgtm/go-alerts-lgtm @@ -0,0 +1,3 @@ +# DO NOT EDIT +# This is a stub file. The actual suite of queries to run is generated +# automatically based on query precision and severity. diff --git a/ql/config/suites/lgtm/go-lgtm b/ql/config/suites/lgtm/go-lgtm new file mode 100644 index 00000000..fa96641e --- /dev/null +++ b/ql/config/suites/lgtm/go-lgtm @@ -0,0 +1,3 @@ +@import "go-alerts-lgtm" +@import "go-metrics-lgtm" +@import "go-util-lgtm" diff --git a/ql/config/suites/lgtm/go-metrics-lgtm b/ql/config/suites/lgtm/go-metrics-lgtm new file mode 100644 index 00000000..a295f30c --- /dev/null +++ b/ql/config/suites/lgtm/go-metrics-lgtm @@ -0,0 +1,2 @@ ++ go-queries/Metrics/FLinesOfCode.ql: /Metrics/Files + @_namespace com.lgtm/go-queries diff --git a/ql/config/suites/lgtm/go-util-lgtm b/ql/config/suites/lgtm/go-util-lgtm new file mode 100644 index 00000000..908e157d --- /dev/null +++ b/ql/config/suites/lgtm/go-util-lgtm @@ -0,0 +1,6 @@ ++ go-queries/definitions.ql + @_namespace com.lgtm/go-queries ++ go-queries/AlertSuppression.ql + @_namespace com.lgtm/go-queries ++ go-queries/filters/ClassifyFiles.ql + @_namespace com.lgtm/go-queries diff --git a/ql/src/.project b/ql/src/.project new file mode 100644 index 00000000..87ee8d5c --- /dev/null +++ b/ql/src/.project @@ -0,0 +1,12 @@ + + + go-queries + + + + + + + com.semmle.plugin.qdt.core.qlnature + + diff --git a/ql/src/.qlpath b/ql/src/.qlpath new file mode 100644 index 00000000..f117e497 --- /dev/null +++ b/ql/src/.qlpath @@ -0,0 +1,5 @@ + + + + /go-queries/go.dbscheme + diff --git a/ql/src/AlertSuppression.ql b/ql/src/AlertSuppression.ql new file mode 100644 index 00000000..9dc9d801 --- /dev/null +++ b/ql/src/AlertSuppression.ql @@ -0,0 +1,78 @@ +/** + * @name Alert suppression + * @description Generates information about alert suppressions. + * @kind alert-suppression + * @id go/alert-suppression + */ + +import go + +/** + * An alert suppression comment. + */ +class SuppressionComment extends Locatable { + string text; + + string annotation; + + SuppressionComment() { + text = this.(LineComment).getText() and + ( + // match `lgtm[...]` anywhere in the comment + annotation = text.regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _) + or + // match `lgtm` at the start of the comment and after semicolon + annotation = text.regexpFind("(?i)(?<=^|;)\\s*lgtm(?!\\B|\\s*\\[)", _, _).trim() + ) + } + + /** Gets the text of this suppression comment, not including delimiters. */ + string getText() { result = text } + + /** Gets the suppression annotation in this comment. */ + string getAnnotation() { result = annotation } + + /** + * Holds if this comment applies to the range from column `startcolumn` of line `startline` + * to column `endcolumn` of line `endline` in file `filepath`. + */ + predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) { + this.getLocation().hasLocationInfo(filepath, startline, _, endline, endcolumn) and + startcolumn = 1 + } + + /** Gets the scope of this suppression. */ + SuppressionScope getScope() { this = result.getSuppressionComment() } +} + +/** + * The scope of an alert suppression comment. + */ +class SuppressionScope extends @locatable { + SuppressionScope() { this instanceof SuppressionComment } + + /** Gets a suppression comment with this scope. */ + SuppressionComment getSuppressionComment() { result = this } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + this.(SuppressionComment).covers(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { result = "suppression range" } +} + +from SuppressionComment c +select c, // suppression comment + c.getText(), // text of suppression comment (excluding delimiters) + c.getAnnotation(), // text of suppression annotation + c.getScope() // scope of suppression diff --git a/ql/src/Customizations.qll b/ql/src/Customizations.qll new file mode 100644 index 00000000..127840de --- /dev/null +++ b/ql/src/Customizations.qll @@ -0,0 +1,12 @@ +/** + * Contains customizations to the standard library. + * + * This module is imported by `go.qll`, so any customizations defined here automatically + * apply to all queries. + * + * Typical examples of customizations include adding new subclasses of abstract classes such as + * `FileSystemAccess`, or the `Source` and `Sink` classes associated with the security queries + * to model frameworks that are not covered by the standard library. + */ + +import go diff --git a/ql/src/InconsistentCode/InconsistentLoopOrientation.go b/ql/src/InconsistentCode/InconsistentLoopOrientation.go new file mode 100644 index 00000000..facf90a8 --- /dev/null +++ b/ql/src/InconsistentCode/InconsistentLoopOrientation.go @@ -0,0 +1,13 @@ +package main + +func zeroOutExcept(a []int, lower int, upper int) { + // zero out everything below index `lower` + for i := lower - 1; i >= 0; i-- { + a[i] = 0 + } + + // zero out everything above index `upper` + for i := upper + 1; i < len(a); i-- { + a[i] = 0 + } +} diff --git a/ql/src/InconsistentCode/InconsistentLoopOrientation.qhelp b/ql/src/InconsistentCode/InconsistentLoopOrientation.qhelp new file mode 100644 index 00000000..4d578b00 --- /dev/null +++ b/ql/src/InconsistentCode/InconsistentLoopOrientation.qhelp @@ -0,0 +1,53 @@ + + + +

+Most for loops either increment a variable until an upper bound is reached, +or decrement a variable until a lower bound is reached. If, instead, the variable is +incremented but checked against a lower bound, or decremented but checked against an +upper bound, then the loop will usually either terminate immediately and never execute +its body, or it will keep iterating indefinitely. Neither is likely to be intentional, +and is most likely the result of a typo. +

+

+The only exception to this are loops whose loop variable is an unsigned integer. In this +case, initializing the variable with an upper bound and then decrementing it while +checking against the same upper bound is unproblematic: the variable is counted down +from the upper bound to zero, and then wraps around to a large positive value, causing +the loop to terminate. This is usually intentional, and hence is not flagged by the query. +

+ +
+ + +

+Examine the loop carefully to check whether its test expression or update statement +are erroneous. +

+ +
+ + +

+In the following example, two loops are used to set all elements of a slice a +outside a range lower..upper to zero. However, the second loop +contains a typo: the loop variable i is decremented instead of incremented, +so i is counted downwards from upper+1 to 0, -1, +-2 and so on. +

+ + + +

+To correct this issue, change the second loop to increment its loop variable instead: +

+ + + +
+ +
  • The Go Programming Language Specification: For statements.
  • +
    +
    diff --git a/ql/src/InconsistentCode/InconsistentLoopOrientation.ql b/ql/src/InconsistentCode/InconsistentLoopOrientation.ql new file mode 100644 index 00000000..4e297840 --- /dev/null +++ b/ql/src/InconsistentCode/InconsistentLoopOrientation.ql @@ -0,0 +1,55 @@ +/** + * @name Inconsistent direction of for loop + * @description A 'for' loop that increments its loop variable but checks it + * against a lower bound, or decrements its loop variable but + * checks it against an upper bound, will either stop iterating + * immediately or keep iterating indefinitely, and is usually + * indicative of a typo. + * @kind problem + * @problem.severity error + * @id go/inconsistent-loop-direction + * @tags correctness + * external/cwe/cwe-835 + * @precision very-high + */ + +import go + +/** + * Holds if `test` bounds `v` in `direction`, which is either `"upward"` + * or `"downward"`. + * + * For example, `x < 42` bounds `x` upward, while `y >= 0` bounds `y` + * downward. + */ +predicate bounds(RelationalComparisonExpr test, Variable v, string direction) { + test.getLesserOperand() = v.getAUse() and direction = "upward" + or + test.getGreaterOperand() = v.getAUse() and direction = "downward" +} + +/** + * Holds if `upd` updates `v` in `direction`, which is either `"upward"` + * or `"downward"`. + * + * For example, `++x` updates `x` upward, while `y--` updates `y` + * downward. + */ +predicate updates(IncDecStmt upd, Variable v, string direction) { + upd.getExpr() = v.getAUse() and + ( + upd instanceof IncStmt and direction = "upward" + or + upd instanceof DecStmt and direction = "downward" + ) +} + +from ForStmt l, Variable v, string d1, string d2 +where + bounds(l.getCond(), v, d1) and + updates(l.getPost(), v, d2) and + d1 != d2 and + // `for u = n; u <= n; u--` is a somewhat common idiom + not (v.getType().getUnderlyingType() instanceof UnsignedIntegerType and d2 = "downward") +select l.getPost(), "This loop counts " + d2 + ", but its variable is $@ " + d1 + ".", l.getCond(), + "bounded" diff --git a/ql/src/InconsistentCode/InconsistentLoopOrientationGood.go b/ql/src/InconsistentCode/InconsistentLoopOrientationGood.go new file mode 100644 index 00000000..69921dda --- /dev/null +++ b/ql/src/InconsistentCode/InconsistentLoopOrientationGood.go @@ -0,0 +1,13 @@ +package main + +func zeroOutExcept(a []int, lower int, upper int) { + // zero out everything below index `lower` + for i := lower - 1; i >= 0; i-- { + a[i] = 0 + } + + // zero out everything above index `upper` + for i := upper + 1; i < len(a); i++ { + a[i] = 0 + } +} diff --git a/ql/src/InconsistentCode/LengthComparisonOffByOne.go b/ql/src/InconsistentCode/LengthComparisonOffByOne.go new file mode 100644 index 00000000..7db63c62 --- /dev/null +++ b/ql/src/InconsistentCode/LengthComparisonOffByOne.go @@ -0,0 +1,15 @@ +package main + +import "strings" + +func containsBad(searchName string, names string) bool { + values := strings.Split(names, ",") + // BAD: index could be equal to length + for i := 0; i <= len(values); i++ { + // When i = length, this access will be out of bounds + if values[i] == searchName { + return true + } + } + return false +} diff --git a/ql/src/InconsistentCode/LengthComparisonOffByOne.qhelp b/ql/src/InconsistentCode/LengthComparisonOffByOne.qhelp new file mode 100644 index 00000000..6aa99a6c --- /dev/null +++ b/ql/src/InconsistentCode/LengthComparisonOffByOne.qhelp @@ -0,0 +1,47 @@ + + + + +

    +Indexing operations on arrays, slices or strings should use an index at most one less +than the length. If the index to be accessed is checked for being less than or equal to the length +(<=), instead of less than the length (<), the index could be out +of bounds. +

    +
    + + +

    +Use less than (<) rather than less than or equals (<=) when +comparing a potential index against a length. For loops that iterate over every element, a better +solution is to use a range loop instead of looping over explicit indexes. +

    +
    + + +

    +The following example shows a method which checks whether a value appears in a comma-separated +list of values: +

    + +

    +A loop using an index variable i is used to iterate over the elements in the comma-separated +list. However, the terminating condition of the loop is incorrectly specified as i <= len(values). +This condition holds when i is equal to len(values), but the access values[i] +in the body of the loop will be out of bounds in this case. +

    +

    +One potential solution would be to replace i <= len(values) with +i < len(values). A better solution is to use a range loop instead, which +avoids the need for explicitly manipulating the index variable: +

    + +
    + + +
  • The Go Programming Language Specification: For statements.
  • +
  • The Go Programming Language Specification: Index expressions.
  • +
    +
    diff --git a/ql/src/InconsistentCode/LengthComparisonOffByOne.ql b/ql/src/InconsistentCode/LengthComparisonOffByOne.ql new file mode 100644 index 00000000..3a18b4d2 --- /dev/null +++ b/ql/src/InconsistentCode/LengthComparisonOffByOne.ql @@ -0,0 +1,100 @@ +/** + * @name Off-by-one comparison against length + * @description An array index is compared with the length of the array, + * and then used in an indexing operation that could be out of bounds. + * @kind problem + * @problem.severity error + * @id go/index-out-of-bounds + * @tags reliability + * correctness + * logic + * external/cwe/cwe-193 + * @precision high + */ + +import go + +newtype TIndex = + VariableIndex(DataFlow::SsaNode v) { v.getAUse() = any(DataFlow::ElementReadNode e).getIndex() } or + ConstantIndex(int v) { v = any(DataFlow::ElementReadNode e).getIndex().getIntValue() + [-1 .. 1] } + +class Index extends TIndex { + string toString() { + exists(DataFlow::SsaNode v | this = VariableIndex(v) | result = v.getSourceVariable().getName()) + or + exists(int v | this = ConstantIndex(v) | result = v.toString()) + } +} + +DataFlow::Node getAUse(Index i) { + i = VariableIndex(any(DataFlow::SsaNode v | result = v.getAUse())) + or + i = ConstantIndex(any(int v | v = result.getIntValue())) +} + +/** + * Gets a call to `len(array)`. + */ +DataFlow::CallNode arrayLen(DataFlow::SsaNode array) { + result = Builtin::len().getACall() and + result.getArgument(0) = array.getAUse() +} + +/** + * Gets a condition that checks that `index` is less than or equal to `array.length`. + */ +ControlFlow::ConditionGuardNode getLengthLEGuard(Index index, DataFlow::SsaNode array) { + result.ensuresLeq(getAUse(index), arrayLen(array), 0) + or + exists(int i, int bias | index = ConstantIndex(i) | + result.ensuresLeq(getAUse(ConstantIndex(i + bias)), arrayLen(array), bias) + ) +} + +/** + * Gets a condition that checks that `index` is not equal to `array.length`. + */ +ControlFlow::ConditionGuardNode getLengthNEGuard(Index index, DataFlow::SsaNode array) { + result.ensuresNeq(getAUse(index), arrayLen(array)) +} + +/** + * Holds if `ea` is a read from `array[index]` in basic block `bb`. + */ +predicate elementRead( + DataFlow::ElementReadNode ea, DataFlow::SsaNode array, Index index, BasicBlock bb +) { + ea.reads(array.getAUse(), getAUse(index)) and + not array.getType().getUnderlyingType() instanceof MapType and + bb = ea.asInstruction().getBasicBlock() +} + +predicate isRegexpMethodCall(DataFlow::MethodCallNode c) { + exists(NamedType regexp, Type recvtp | + regexp.getName() = "Regexp" and recvtp = c.getReceiver().getType() + | + recvtp = regexp or recvtp.(PointerType).getBaseType() = regexp + ) +} + +from + ControlFlow::ConditionGuardNode cond, DataFlow::SsaNode array, Index index, + DataFlow::ElementReadNode ea, BasicBlock bb +where + // there is a comparison `index <= len(array)` + cond = getLengthLEGuard(index, array) and + // there is a read from `array[index]` + elementRead(ea, array, index, bb) and + // and the read is guarded by the comparison + cond.dominates(bb) and + // but the read is not guarded by another check that `index != len(array)` + not getLengthNEGuard(index, array).dominates(bb) and + // and it is not additionally guarded by a stronger index check + not exists(Index index2, int i, int i2 | + index = ConstantIndex(i) and index2 = ConstantIndex(i2) and i < i2 + | + getLengthLEGuard(index2, array).dominates(bb) + ) and + not isRegexpMethodCall(array.getInit()) +select cond.getCondition(), + "Off-by-one index comparison against length may lead to out-of-bounds $@.", ea, "read" diff --git a/ql/src/InconsistentCode/LengthComparisonOffByOneGood.go b/ql/src/InconsistentCode/LengthComparisonOffByOneGood.go new file mode 100644 index 00000000..6c0bcb46 --- /dev/null +++ b/ql/src/InconsistentCode/LengthComparisonOffByOneGood.go @@ -0,0 +1,14 @@ +package main + +import "strings" + +func containsGood(searchName string, names string) bool { + values := strings.Split(names, ",") + // GOOD: Avoid using indexes, use range loop instead + for _, name := range values { + if name == searchName { + return true + } + } + return true +} diff --git a/ql/src/InconsistentCode/MistypedExponentiation.go b/ql/src/InconsistentCode/MistypedExponentiation.go new file mode 100644 index 00000000..a9f1e639 --- /dev/null +++ b/ql/src/InconsistentCode/MistypedExponentiation.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println(2 ^ 32) // should be 1 << 32 +} diff --git a/ql/src/InconsistentCode/MistypedExponentiation.qhelp b/ql/src/InconsistentCode/MistypedExponentiation.qhelp new file mode 100644 index 00000000..acaa0aa0 --- /dev/null +++ b/ql/src/InconsistentCode/MistypedExponentiation.qhelp @@ -0,0 +1,36 @@ + + + + +

    + The caret symbol (^) is sometimes used to represent + exponentiation but in Go, as in many C-like languages, it represents the + bitwise exclusive-or operation. The expression as 2^32 thus + evaluates the number 34, not 232, and it is likely that patterns + such as this are mistakes. +

    +
    + + +

    + To compute 2EXP, 1 << EXP can be + used. For constant exponents, 1eEXP can be used to find + 10EXP. In other cases, there is math.Pow + in the Go standard library which provides this functionality. +

    +
    + + +

    + The example below prints 34 and not 232 (4294967296). +

    + + +
    + + +
  • GCC Bugzilla: GCC should warn about 2^16 and 2^32 and 2^64
  • +
    +
    diff --git a/ql/src/InconsistentCode/MistypedExponentiation.ql b/ql/src/InconsistentCode/MistypedExponentiation.ql new file mode 100644 index 00000000..3fbb7b34 --- /dev/null +++ b/ql/src/InconsistentCode/MistypedExponentiation.ql @@ -0,0 +1,37 @@ +/** + * @name Bitwise exclusive-or used like exponentiation + * @description Using ^ as exponentiation is a mistake, as it is the bitwise exclusive-or operator. + * @kind problem + * @problem.severity warning + * @id go/mistyped-exponentiation + * @precision high + */ + +import go + +/** Holds if `e` is not 0 and is either an octal or hexadecimal literal, or the number one. */ +predicate maybeXorBitPattern(Expr e) { + // 0 makes no sense as an xor bit pattern + not e.getNumericValue() = 0 and + // include octal and hex literals + e.(IntLit).getText().matches("0%") + or + e.getNumericValue() = 1 +} + +from XorExpr xe, Expr lhs, Expr rhs +where + lhs = xe.getLeftOperand() and + rhs = xe.getRightOperand() and + exists(lhs.getNumericValue()) and + not maybeXorBitPattern(lhs) and + ( + not maybeXorBitPattern(rhs) and + rhs.getIntValue() >= 0 + or + exists(Ident id | id = xe.getRightOperand() | + id.getName().regexpMatch("(?i)_*((exp(onent)?)|pow(er)?)") + ) + ) +select xe, + "This expression uses the bitwise exclusive-or operator when exponentiation was likely meant." diff --git a/ql/src/InconsistentCode/WhitespaceContradictsPrecedence.go b/ql/src/InconsistentCode/WhitespaceContradictsPrecedence.go new file mode 100644 index 00000000..06d83742 --- /dev/null +++ b/ql/src/InconsistentCode/WhitespaceContradictsPrecedence.go @@ -0,0 +1,5 @@ +package main + +func isBitSetBad(x int, pos uint) bool { + return x&1< + + + +

    +Nested expressions where the spacing around operators suggests a different +grouping than that imposed by the Go operator precedence rules are problematic: +they could indicate a bug where the author of the code misunderstood the precedence +rules. Even if there is no a bug, the spacing could be confusing to people who +read the code. +

    +
    + + +

    +Make sure that the spacing around operators reflects operator precedence, or use parentheses to +clarify grouping. +

    +
    + + +

    +Consider the following function intended for checking whether the bit at position `pos` of the variable `x` +is set: +

    + +

    +Here, the spacing around & and << suggests the grouping +x & (1<<pos). However, in Go & and << have +the same precedence and hence are evaluated left to right, so the expression is actually equivalent to +(x & 1) << pos. +

    +

    +To fix this issue and give the expression its intended semantics, parentheses should be used like this: +

    + +
    + + +
  • The Go Programming Language Specification: Operator precedence.
  • +
    +
    diff --git a/ql/src/InconsistentCode/WhitespaceContradictsPrecedence.ql b/ql/src/InconsistentCode/WhitespaceContradictsPrecedence.ql new file mode 100644 index 00000000..c2d2f80a --- /dev/null +++ b/ql/src/InconsistentCode/WhitespaceContradictsPrecedence.ql @@ -0,0 +1,88 @@ +/** + * @name Whitespace contradicts operator precedence + * @description Nested expressions where the formatting contradicts the grouping enforced by operator precedence + * are difficult to read and may even indicate a bug. + * @kind problem + * @problem.severity warning + * @id go/whitespace-contradicts-precedence + * @tags maintainability + * correctness + * external/cwe/cwe-783 + * @precision very-high + */ + +import go + +/** + * A nested associative expression. + * + * That is, a binary expression of the form `x op y`, which is itself an operand + * (say, the left) of another binary expression `(x op y) op' z` such that + * `(x op y) op' z = x op (y op' z)`, disregarding overflow. + */ +class AssocNestedExpr extends BinaryExpr { + AssocNestedExpr() { + exists(BinaryExpr parent, int idx | this = parent.getChildExpr(idx) | + // +, *, &&, || and the bitwise operations are associative + ( + this instanceof AddExpr or + this instanceof MulExpr or + this instanceof BitwiseExpr or + this instanceof LogicalBinaryExpr + ) and + parent.getOperator() = this.getOperator() + or + // (x*y)/z = x*(y/z) + this instanceof MulExpr and parent instanceof DivExpr and idx = 0 + or + // (x/y)%z = x/(y%z) + this instanceof DivExpr and parent instanceof ModExpr and idx = 0 + or + // (x+y)-z = x+(y-z) + this instanceof AddExpr and parent instanceof SubExpr and idx = 0 + ) + } +} + +/** + * A binary expression nested inside another binary expression where the relative + * precedence of the two operators is unlikely to cause confusion. + */ +class HarmlessNestedExpr extends BinaryExpr { + HarmlessNestedExpr() { + exists(BinaryExpr parent | this = parent.getAChildExpr() | + parent instanceof Comparison and + (this instanceof ArithmeticExpr or this instanceof ShiftExpr) + or + parent instanceof LogicalExpr and this instanceof Comparison + ) + } +} + +/** + * Holds if `inner` is an operand of `outer`, and the relative precedence + * may not be immediately clear, but is important for the semantics of + * the expression (that is, the operators are not associative). + */ +predicate interestingNesting(BinaryExpr inner, BinaryExpr outer) { + inner = outer.getAChildExpr() and + not inner instanceof AssocNestedExpr and + not inner instanceof HarmlessNestedExpr +} + +/** Gets the number of whitespace characters around the operator `op` of `be`. */ +int getWhitespaceAroundOperator(BinaryExpr be, string op) { + exists(string file, int line, int left, int right | + be.getLeftOperand().hasLocationInfo(file, _, _, line, left) and + be.getRightOperand().hasLocationInfo(file, line, right, _, _) and + op = be.getOperator() and + result = (right - left - op.length() - 1) / 2 + ) +} + +from BinaryExpr inner, BinaryExpr outer, string innerOp, string outerOp +where + interestingNesting(inner, outer) and + getWhitespaceAroundOperator(inner, innerOp) > getWhitespaceAroundOperator(outer, outerOp) +select outer, + innerOp + " is evaluated before " + outerOp + ", but whitespace suggests the opposite." diff --git a/ql/src/InconsistentCode/WhitespaceContradictsPrecedenceGood.go b/ql/src/InconsistentCode/WhitespaceContradictsPrecedenceGood.go new file mode 100644 index 00000000..339c4f71 --- /dev/null +++ b/ql/src/InconsistentCode/WhitespaceContradictsPrecedenceGood.go @@ -0,0 +1,5 @@ +package main + +func isBitSetGood(x int, pos uint) bool { + return x&(1< + + + +

    +There are a number of problems associated with a high number of lines of code: +

    +
      +
    • It can be difficult to understand and maintain, even with good tool support.
    • +
    • It increases the likelihood of multiple developers needing to work on the same + file at once, and it therefore increases the likelihood of merge conflicts.
    • +
    • It may increase network traffic if you use a version control system that requires + the whole file to be transmitted even for a tiny change.
    • +
    • It may arise as a result of bundling many unrelated things into the same file, + and so it can indicate weak code organization.
    • +
    +
    + + +

    +The solution depends on the reason for the high number of lines: +

    +
      +
    • If the file contains one or more very large functions, you should decompose them + into smaller functions by means of the Extract Function refactoring.
    • +
    • If the file contains many smaller functions, you should try to split up the file + into multiple smaller files.
    • +
    • If the file has been automatically generated by a tool, no changes are required + because the file will not be maintained by a programmer.
    • +
    +
    + + +
  • M. Fowler, Refactoring. Addison-Wesley, 1999.
  • +
    +
    diff --git a/ql/src/Metrics/FLinesOfCode.ql b/ql/src/Metrics/FLinesOfCode.ql new file mode 100644 index 00000000..aad29d46 --- /dev/null +++ b/ql/src/Metrics/FLinesOfCode.ql @@ -0,0 +1,18 @@ +/** + * @name Lines of code in files + * @description Measures the number of lines of code in each file, ignoring lines that + * contain only comments or whitespace. + * @kind metric + * @treemap.warnOn highValues + * @metricType file + * @metricAggregate avg sum max + * @precision very-high + * @id go/lines-of-code-in-files + * @tags maintainability + */ + +import go + +from File f, int n +where n = f.getNumberOfLinesOfCode() +select f, n order by n desc diff --git a/ql/src/Metrics/FLinesOfComment.qhelp b/ql/src/Metrics/FLinesOfComment.qhelp new file mode 100644 index 00000000..3d713fee --- /dev/null +++ b/ql/src/Metrics/FLinesOfComment.qhelp @@ -0,0 +1,24 @@ + + + + +

    +This metric measures the number of comment lines per file. A low number of comments +may indicate files that are difficult to understand due to poor documentation. +

    +
    + + +

    +Consider if the file needs more documentation. Most files should have at least a comment +explaining their purpose. +

    +
    + + +
  • Jeff Atwood. Avoiding Undocumentation. 2005.
  • +
  • Steve McConnell. Code Complete. 2nd Edition. Microsoft Press. 2004.
  • +
    +
    diff --git a/ql/src/Metrics/FLinesOfComment.ql b/ql/src/Metrics/FLinesOfComment.ql new file mode 100644 index 00000000..2d4fbfca --- /dev/null +++ b/ql/src/Metrics/FLinesOfComment.ql @@ -0,0 +1,17 @@ +/** + * @name Lines of comments in files + * @description Files with few lines of comment might not have sufficient documentation + * to make them understandable. + * @kind metric + * @treemap.warnOn lowValues + * @metricType file + * @metricAggregate avg sum max + * @precision very-high + * @id go/lines-of-comments-in-files + * @tags documentation + */ + +import go + +from File f +select f, f.getNumberOfLinesOfComments() as n order by n desc diff --git a/ql/src/RedundantCode/Clones.qll b/ql/src/RedundantCode/Clones.qll new file mode 100644 index 00000000..48650674 --- /dev/null +++ b/ql/src/RedundantCode/Clones.qll @@ -0,0 +1,166 @@ +/** + * Provides predicates for hashing AST nodes by structure. + */ + +import go + +/** + * The root of a sub-AST that should be hashed. + */ +abstract class HashRoot extends AstNode { } + +/** + * An AST node that can be hashed. + */ +class HashableNode extends AstNode { + HashableNode() { + this instanceof HashRoot or + getParent() instanceof HashableNode + } + + /** + * An opaque integer describing the type of this AST node. + */ + int getKind() { + exists(int baseKind | + // map expression kinds to even positive numbers + baseKind = this.(Expr).getKind() and + result = (baseKind + 1) * 2 + or + // map statement kinds to odd positive numbers + baseKind = this.(Stmt).getKind() and + result = baseKind * 2 + 1 + or + // map declaration kinds to even negative numbers + baseKind = this.(Decl).getKind() and + result = -(baseKind + 1) * 2 + or + // map declaration specifier kinds to odd negative numbers + baseKind = this.(Spec).getKind() and + result = -(baseKind * 2 + 1) + or + // give files kind zero + this instanceof File and + baseKind = 0 and + result = 0 + ) + } + + /** + * Gets the value of this AST node, or the empty string if it does not have one. + */ + string getValue() { + literals(this, result, _) + or + not literals(this, _, _) and + result = "" + } + + /** + * Computes a hash for this AST node based on its structure. + */ + abstract HashedNode hash(); +} + +/** + * An AST node without any children. + */ +class HashableNullaryNode extends HashableNode { + HashableNullaryNode() { not exists(getAChild()) } + + predicate unpack(int kind, string value) { kind = getKind() and value = getValue() } + + override HashedNode hash() { + exists(int kind, string value | unpack(kind, value) | result = MkHashedNullaryNode(kind, value)) + } +} + +/** + * An AST node with exactly one child, which is at position zero. + */ +class HashableUnaryNode extends HashableNode { + HashableUnaryNode() { getNumChild() = 1 and exists(getChild(0)) } + + predicate unpack(int kind, string value, HashedNode child) { + kind = getKind() and value = getValue() and child = getChild(0).(HashableNode).hash() + } + + override HashedNode hash() { + exists(int kind, string value, HashedNode child | unpack(kind, value, child) | + result = MkHashedUnaryNode(kind, value, child) + ) + } +} + +/** + * An AST node with exactly two children, which are at positions zero and one. + */ +class HashableBinaryNode extends HashableNode { + HashableBinaryNode() { getNumChild() = 2 and exists(getChild(0)) and exists(getChild(1)) } + + predicate unpack(int kind, string value, HashedNode left, HashedNode right) { + kind = getKind() and + value = getValue() and + left = getChild(0).(HashableNode).hash() and + right = getChild(1).(HashableNode).hash() + } + + override HashedNode hash() { + exists(int kind, string value, HashedNode left, HashedNode right | + unpack(kind, value, left, right) + | + result = MkHashedBinaryNode(kind, value, left, right) + ) + } +} + +/** + * An AST node with more than two children, or with non-consecutive children. + */ +class HashableNAryNode extends HashableNode { + HashableNAryNode() { + exists(int n | n = strictcount(getAChild()) | n > 2 or not exists(getChild([0 .. n - 1]))) + } + + predicate unpack(int kind, string value, HashedChildren children) { + kind = getKind() and value = getValue() and children = hashChildren() + } + + predicate childAt(int i, HashedNode child, HashedChildren rest) { + child = getChild(i).(HashableNode).hash() and rest = hashChildren(i + 1) + } + + override HashedNode hash() { + exists(int kind, string value, HashedChildren children | unpack(kind, value, children) | + result = MkHashedNAryNode(kind, value, children) + ) + } + + HashedChildren hashChildren() { result = hashChildren(0) } + + HashedChildren hashChildren(int i) { + i = max(int n | exists(getChild(n))) + 1 and result = Nil() + or + exists(HashedNode child, HashedChildren rest | childAt(i, child, rest) | + result = AHashedChild(i, child, rest) + ) + } +} + +newtype HashedNode = + MkHashedNullaryNode(int kind, string value) { any(HashableNullaryNode nd).unpack(kind, value) } or + MkHashedUnaryNode(int kind, string value, HashedNode child) { + any(HashableUnaryNode nd).unpack(kind, value, child) + } or + MkHashedBinaryNode(int kind, string value, HashedNode left, HashedNode right) { + any(HashableBinaryNode nd).unpack(kind, value, left, right) + } or + MkHashedNAryNode(int kind, string value, HashedChildren children) { + any(HashableNAryNode nd).unpack(kind, value, children) + } + +newtype HashedChildren = + Nil() or + AHashedChild(int i, HashedNode child, HashedChildren rest) { + exists(HashableNAryNode nd | nd.childAt(i, child, rest)) + } diff --git a/ql/src/RedundantCode/CompareIdenticalValues.go b/ql/src/RedundantCode/CompareIdenticalValues.go new file mode 100644 index 00000000..3d470d4b --- /dev/null +++ b/ql/src/RedundantCode/CompareIdenticalValues.go @@ -0,0 +1,12 @@ +package main + +type Rectangle struct { + x, y, width, height float64 +} + +func (r *Rectangle) containsBad(x, y float64) bool { + return r.x <= x && + y <= y && + x <= r.x+r.width && + y <= r.y+r.height +} diff --git a/ql/src/RedundantCode/CompareIdenticalValues.qhelp b/ql/src/RedundantCode/CompareIdenticalValues.qhelp new file mode 100644 index 00000000..c44e3bd0 --- /dev/null +++ b/ql/src/RedundantCode/CompareIdenticalValues.qhelp @@ -0,0 +1,37 @@ + + + + +

    +Comparing two identical expressions typically indicates a mistake such as a missing qualifier or a +misspelled variable name. +

    +
    + + +

    +Carefully inspect the comparison to determine whether it is a symptom of a bug. +

    +
    + + +

    +In the example below, the method Rectangle.contains is intended to check whether a +point (x, y) lies inside a rectangle r given by its origin +(r.x, r.y), its width r.width, and its height r.height. +

    + + + +

    +Note, however, that on line 9 the programmer forgot to qualify r.y, +thus ending up comparing the argument y against itself. The comparison +should be fixed accordingly: +

    + + +
    + +
    diff --git a/ql/src/RedundantCode/CompareIdenticalValues.ql b/ql/src/RedundantCode/CompareIdenticalValues.ql new file mode 100644 index 00000000..5ce12c4f --- /dev/null +++ b/ql/src/RedundantCode/CompareIdenticalValues.ql @@ -0,0 +1,27 @@ +/** + * @name Comparison of identical values + * @description If the same expression occurs on both sides of a comparison + * operator, the operator is redundant, and probably indicates a mistake. + * @kind problem + * @problem.severity warning + * @id go/comparison-of-identical-expressions + * @tags correctness + * external/cwe/cwe-570 + * external/cwe/cwe-571 + * @precision very-high + */ + +import go + +from Comparison cmp, Expr l +where + l = cmp.getLeftOperand() and + l.getGlobalValueNumber() = cmp.getRightOperand().getGlobalValueNumber() and + // whitelist floats, where self-comparison may be used for NaN checks + not l.getType().getUnderlyingType() instanceof FloatType and + // whitelist comparisons of symbolic constants to literal constants; these are often feature flags + not exists(DeclaredConstant decl | + cmp.getAnOperand() = decl.getAReference() and + cmp.getAnOperand() instanceof BasicLit + ) +select cmp, "This expression compares $@ to itself.", cmp.getLeftOperand(), "an expression" diff --git a/ql/src/RedundantCode/CompareIdenticalValuesGood.go b/ql/src/RedundantCode/CompareIdenticalValuesGood.go new file mode 100644 index 00000000..bf676fa7 --- /dev/null +++ b/ql/src/RedundantCode/CompareIdenticalValuesGood.go @@ -0,0 +1,8 @@ +package main + +func (r *Rectangle) containsGood(x, y float64) bool { + return r.x <= x && + r.y <= y && + x <= r.x+r.width && + y <= r.y+r.height +} diff --git a/ql/src/RedundantCode/DeadStoreOfField.go b/ql/src/RedundantCode/DeadStoreOfField.go new file mode 100644 index 00000000..2330f48b --- /dev/null +++ b/ql/src/RedundantCode/DeadStoreOfField.go @@ -0,0 +1,9 @@ +package main + +type counter struct { + val int +} + +func (c counter) reset() { + c.val = 0 +} diff --git a/ql/src/RedundantCode/DeadStoreOfField.qhelp b/ql/src/RedundantCode/DeadStoreOfField.qhelp new file mode 100644 index 00000000..7bd6dbc2 --- /dev/null +++ b/ql/src/RedundantCode/DeadStoreOfField.qhelp @@ -0,0 +1,42 @@ + + + + +

    +A value is assigned to a field, but its value is never read. This means that the assignment +has no effect, and could indicate a logic error or incomplete code. +

    +
    + + +

    +Examine the assignment closely to determine whether it is redundant, or whether it is perhaps +a symptom of another bug. +

    +
    + + +

    +The following example shows a simple struct type wrapping an integer counter with a +method reset that sets the counter to zero. +

    + +

    +However, the receiver variable of reset is declared to be of type +counter, not *counter, so the receiver value is passed into the method +by value, not by reference. Consequently, the method does not actually mutate its receiver as +intended. +

    +

    +To fix this, change the type of the receiver variable to *counter: +

    + +
    + + +
  • Go Frequently Asked Questions: Should I define methods on values or pointers?
  • +
  • The Go Programming Language Specification: Method declarations.
  • +
    +
    diff --git a/ql/src/RedundantCode/DeadStoreOfField.ql b/ql/src/RedundantCode/DeadStoreOfField.ql new file mode 100644 index 00000000..064912d3 --- /dev/null +++ b/ql/src/RedundantCode/DeadStoreOfField.ql @@ -0,0 +1,66 @@ +/** + * @name Useless assignment to field + * @description An assignment to a field that is not used later on has no effect. + * @kind problem + * @problem.severity warning + * @id go/useless-assignment-to-field + * @tags maintainability + * external/cwe/cwe-563 + * @precision very-high + */ + +import go + +/** + * Holds if `nd` escapes, that is, its value flows into the heap or across + * function boundaries. + */ +predicate escapes(DataFlow::Node nd) { + // if `nd` is written to something that is not an SSA variable (such as + // a global variable, a field or an array element), then it escapes + exists(Write w | + nd = w.getRhs() and + not w.definesSsaVariable(_, _) + ) + or + // if `nd` is used as an index into an array or similar, then it escapes + exists(IndexExpr idx | nd.asExpr() = idx.getIndex()) + or + // if `nd` is used in an (in-)equality comparison, then it escapes + exists(EqualityTestExpr eq | nd.asExpr() = eq.getAnOperand()) + or + // if `nd` is returned from a function, then it escapes + nd instanceof DataFlow::ResultNode + or + // if `nd` is sent over a channel, then it escapes + exists(SendStmt s | nd.asExpr() = s.getValue()) + or + // if `nd` is passed to a function, then it escapes + nd instanceof DataFlow::ArgumentNode + or + // if `nd` has its address taken, then it escapes + exists(AddressExpr ae | nd.asExpr() = ae.getOperand()) + or + // if `nd` is used as to look up a method with a pointer receiver, then it escapes + exists(SelectorExpr sel | nd.asExpr() = sel.getBase() | + exists(Method m | sel = m.getAReference() | m.getReceiverType() instanceof PointerType) + or + // if we cannot resolve a reference, we make worst-case assumptions + not exists(sel.(Name).getTarget()) + ) + or + // if `nd` flows into something that escapes, then it escapes + escapes(nd.getASuccessor()) +} + +from Write w, LocalVariable v, Field f +where + // `w` writes `f` on `v` + w.writesField(v.getARead(), f, _) and + // but `f` is never read on `v` + not exists(Read r | r.readsField(v.getARead(), f)) and + // exclude pointer-typed `v`; there may be reads through an alias + not v.getType().getUnderlyingType() instanceof PointerType and + // exclude escaping `v`; there may be reads in other functions + not exists(Read r | r.reads(v) | escapes(r)) +select w, "This assignment to " + f + " is useless since its value is never read." diff --git a/ql/src/RedundantCode/DeadStoreOfFieldGood.go b/ql/src/RedundantCode/DeadStoreOfFieldGood.go new file mode 100644 index 00000000..af7d0cce --- /dev/null +++ b/ql/src/RedundantCode/DeadStoreOfFieldGood.go @@ -0,0 +1,5 @@ +package main + +func (c *counter) resetGood() { + c.val = 0 +} diff --git a/ql/src/RedundantCode/DeadStoreOfLocal.qhelp b/ql/src/RedundantCode/DeadStoreOfLocal.qhelp new file mode 100644 index 00000000..fd8d49d6 --- /dev/null +++ b/ql/src/RedundantCode/DeadStoreOfLocal.qhelp @@ -0,0 +1,45 @@ + + + + +

    +A value is assigned to a variable, but either it is never read, or its value is +always overwritten before being read. This means that the original assignment +has no effect, and could indicate a logic error or incomplete code. +

    +
    + + +

    +Remove assignments to variables that are immediately overwritten, or use the +blank identifier _ as a placeholder for return values that are +never used. +

    +
    + + +

    +In the following example, a value is assigned to a, but then +immediately overwritten, a value is assigned to b and never used, +and finally, the results of a call to fmt.Println are assigned to +two temporary variables, which are then immediately overwritten by a call to +function. +

    + +

    +The result of calculateValue is never used, and +if calculateValue is a side-effect free function, those assignments +can be removed. To ignore all the return values of fmt.Println, you +can simply not assign it to any variables. To ignore only certain return values, +use _. +

    + +
    + + +
  • Wikipedia: Dead store.
  • +
  • The Go Programming Language Specification: Blank identifier.
  • +
    +
    diff --git a/ql/src/RedundantCode/DeadStoreOfLocal.ql b/ql/src/RedundantCode/DeadStoreOfLocal.ql new file mode 100644 index 00000000..9e6bd8b9 --- /dev/null +++ b/ql/src/RedundantCode/DeadStoreOfLocal.ql @@ -0,0 +1,42 @@ +/** + * @name Useless assignment to local variable + * @description An assignment to a local variable that is not used later on, or whose value is always + * overwritten, has no effect. + * @kind problem + * @problem.severity warning + * @id go/useless-assignment-to-local + * @tags maintainability + * external/cwe/cwe-563 + * @precision very-high + */ + +import go + +/** Holds if `nd` is an initializer that we do not want to flag for this query. */ +predicate isSimple(IR::Instruction nd) { + exists(Expr e | + e.isConst() or + e.(CompositeLit).getNumElement() = 0 + | + nd = IR::evalExprInstruction(e) + ) + or + nd = IR::implicitInitInstruction(_) + or + // don't flag parameters + nd instanceof IR::ReadArgumentInstruction +} + +from IR::Instruction def, SsaSourceVariable target, IR::Instruction rhs +where + def.writes(target, rhs) and + not exists(SsaExplicitDefinition ssa | ssa.getInstruction() = def) and + // exclude assignments in dead code + def.getBasicBlock() instanceof ReachableBasicBlock and + // exclude assignments with default values or simple expressions + not isSimple(rhs) and + // exclude variables that are not used at all + exists(target.getAUse()) and + // exclude variables with indirect references + not target.mayHaveIndirectReferences() +select def, "This definition of " + target + " is never used." diff --git a/ql/src/RedundantCode/DeadStoreOfLocalBad.go b/ql/src/RedundantCode/DeadStoreOfLocalBad.go new file mode 100644 index 00000000..a5ef03fd --- /dev/null +++ b/ql/src/RedundantCode/DeadStoreOfLocalBad.go @@ -0,0 +1,19 @@ +package main + +import "fmt" + +func main() { + a := calculateValue() + a = 2 + + b := calculateValue() + + ignore, ignore1 := fmt.Println(a) + + ignore, ignore1, err := function() + if err != nil { + panic(err) + } + + fmt.Println(a) +} diff --git a/ql/src/RedundantCode/DeadStoreOfLocalGood.go b/ql/src/RedundantCode/DeadStoreOfLocalGood.go new file mode 100644 index 00000000..dc84e7ff --- /dev/null +++ b/ql/src/RedundantCode/DeadStoreOfLocalGood.go @@ -0,0 +1,16 @@ +package main + +import "fmt" + +func main() { + a := 2 + + fmt.Println(a) + + _, _, err := function() + if err != nil { + panic(err) + } + + fmt.Println(a) +} diff --git a/ql/src/RedundantCode/DuplicateBranches.go b/ql/src/RedundantCode/DuplicateBranches.go new file mode 100644 index 00000000..f4bc36b6 --- /dev/null +++ b/ql/src/RedundantCode/DuplicateBranches.go @@ -0,0 +1,9 @@ +package main + +func abs(x int) int { + if x >= 0 { + return x + } else { + return x + } +} diff --git a/ql/src/RedundantCode/DuplicateBranches.qhelp b/ql/src/RedundantCode/DuplicateBranches.qhelp new file mode 100644 index 00000000..7ffa26cb --- /dev/null +++ b/ql/src/RedundantCode/DuplicateBranches.qhelp @@ -0,0 +1,36 @@ + + + + +

    +If the 'then' and 'else' branches of an 'if' statement are identical, this suggests a copy-paste +error where the first branch was copied and then not properly adjusted. +

    +
    + + +

    +Examine the two branches to find out what operations were meant to perform. If both the branches +and the conditions that they check are identical, then the second branch is duplicate code +that can be deleted. If the branches are really meant to perform the same operations, it may be clearer to just have a single branch that checks the disjunction of both conditions. +

    +
    + + +

    +The example below shows a buggy implementation of the absolute-value function which checks the sign +of its argument, but then returns the same value regardless of the outcome of the check: +

    + +

    +Clearly, the 'else' branch should return -x instead: +

    + +
    + + +
  • The Go Programming Language Specification: If statements.
  • +
    +
    diff --git a/ql/src/RedundantCode/DuplicateBranches.ql b/ql/src/RedundantCode/DuplicateBranches.ql new file mode 100644 index 00000000..c6aa7523 --- /dev/null +++ b/ql/src/RedundantCode/DuplicateBranches.ql @@ -0,0 +1,25 @@ +/** + * @name Duplicate 'if' branches + * @description If the 'then' and 'else' branches of an 'if' statement are identical, the + * conditional may be superfluous, or it may indicate a mistake. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id go/duplicate-branches + * @tags maintainability + * correctness + * external/cwe/cwe-561 + */ + +import Clones + +class HashedBranch extends HashRoot, Stmt { + HashedBranch() { exists(IfStmt is | this = is.getThen() or this = is.getElse()) } +} + +from IfStmt is, HashableNode thenBranch, HashableNode elseBranch +where + thenBranch = is.getThen() and + elseBranch = is.getElse() and + thenBranch.hash() = elseBranch.hash() +select is.getCond(), "The 'then' and 'else' branches of this if statement are identical." diff --git a/ql/src/RedundantCode/DuplicateBranchesGood.go b/ql/src/RedundantCode/DuplicateBranchesGood.go new file mode 100644 index 00000000..9497c7a1 --- /dev/null +++ b/ql/src/RedundantCode/DuplicateBranchesGood.go @@ -0,0 +1,9 @@ +package main + +func absGood(x int) int { + if x >= 0 { + return x + } else { + return -x + } +} diff --git a/ql/src/RedundantCode/DuplicateCondition.go b/ql/src/RedundantCode/DuplicateCondition.go new file mode 100644 index 00000000..545cd6d8 --- /dev/null +++ b/ql/src/RedundantCode/DuplicateCondition.go @@ -0,0 +1,11 @@ +package main + +func controller(msg string) { + if msg == "start" { + start() + } else if msg == "start" { + stop() + } else { + panic("Message not understood.") + } +} diff --git a/ql/src/RedundantCode/DuplicateCondition.qhelp b/ql/src/RedundantCode/DuplicateCondition.qhelp new file mode 100644 index 00000000..da0fd474 --- /dev/null +++ b/ql/src/RedundantCode/DuplicateCondition.qhelp @@ -0,0 +1,38 @@ + + + + +

    +If two conditions in an 'if'-'else if' chain are identical, the second condition will never +hold. This most likely indicates a copy-paste error where the first condition was copied +and then not properly adjusted. +

    +
    + + +

    +Examine the two conditions to find out what they were meant to check. If both the conditions +and the branches that depend on them are identical, then the second branch is duplicate code +that can be deleted. Otherwise, the second condition needs to be adjusted. +

    +
    + + +

    +In the example below, the function controller checks its parameter msg +to determine what operation it is meant to perform. However, the comparison in the 'else if' is +identical to the comparison in the 'if', so this branch will never be taken. +

    + +

    +Most likely, the 'else if' branch should compare msg to "stop": +

    + +
    + + +
  • The Go Programming Language Specification: If statements.
  • +
    +
    diff --git a/ql/src/RedundantCode/DuplicateCondition.ql b/ql/src/RedundantCode/DuplicateCondition.ql new file mode 100644 index 00000000..ac3c586e --- /dev/null +++ b/ql/src/RedundantCode/DuplicateCondition.ql @@ -0,0 +1,33 @@ +/** + * @name Duplicate 'if' condition + * @description If two conditions in an 'if'-'else if' chain are identical, the + * second condition will never hold. + * @kind problem + * @problem.severity error + * @id go/duplicate-condition + * @tags maintainability + * correctness + * external/cwe/cwe-561 + * @precision very-high + */ + +import go + +/** Gets the `i`th condition in the `if`-`else if` chain starting at `stmt`. */ +Expr getCondition(IfStmt stmt, int i) { + i = 0 and result = stmt.getCond() + or + exists(IfStmt elsif | elsif = stmt.getElse() | + not exists(elsif.getInit()) and + result = getCondition(stmt.getElse(), i - 1) + ) +} + +/** Gets the global value number of `e`, which is the `i`th condition of `is`. */ +GVN conditionGVN(IfStmt is, int i, Expr e) { + e = getCondition(is, i) and result = e.getGlobalValueNumber() +} + +from IfStmt is, Expr e, Expr f, int i, int j +where conditionGVN(is, i, e) = conditionGVN(is, j, f) and i < j +select f, "This condition is a duplicate of $@.", e, "an earlier condition" diff --git a/ql/src/RedundantCode/DuplicateConditionGood.go b/ql/src/RedundantCode/DuplicateConditionGood.go new file mode 100644 index 00000000..a2a4e194 --- /dev/null +++ b/ql/src/RedundantCode/DuplicateConditionGood.go @@ -0,0 +1,11 @@ +package main + +func controllerGood(msg string) { + if msg == "start" { + start() + } else if msg == "stop" { + stop() + } else { + panic("Message not understood.") + } +} diff --git a/ql/src/RedundantCode/DuplicateSwitchCase.go b/ql/src/RedundantCode/DuplicateSwitchCase.go new file mode 100644 index 00000000..1c902c13 --- /dev/null +++ b/ql/src/RedundantCode/DuplicateSwitchCase.go @@ -0,0 +1,12 @@ +package main + +func controller(msg string) { + switch { + case msg == "start": + start() + case msg == "start": + stop() + default: + panic("Message not understood.") + } +} diff --git a/ql/src/RedundantCode/DuplicateSwitchCase.qhelp b/ql/src/RedundantCode/DuplicateSwitchCase.qhelp new file mode 100644 index 00000000..4f377b7b --- /dev/null +++ b/ql/src/RedundantCode/DuplicateSwitchCase.qhelp @@ -0,0 +1,38 @@ + + + + +

    +If two cases in a 'switch' statement are identical, the second case will never be executed. +This most likely indicates a copy-paste error where the first case was copied and then not properly +adjusted. +

    +
    + + +

    +Examine the two cases to find out what they were meant to check. If both the conditions +and the bodies are identical, then the second case is duplicate code that can be deleted. Otherwise, +the second case needs to be adjusted. +

    +
    + + +

    +In the example below, the function controller checks its parameter msg +to determine what operation it is meant to perform. However, the condition of the second case is +identical to that of the first, so this case will never be executed. +

    + +

    +Most likely, the second case should compare msg to "stop": +

    + +
    + + +
  • The Go Programming Language Specification: Switch statements.
  • +
    +
    diff --git a/ql/src/RedundantCode/DuplicateSwitchCase.ql b/ql/src/RedundantCode/DuplicateSwitchCase.ql new file mode 100644 index 00000000..46956347 --- /dev/null +++ b/ql/src/RedundantCode/DuplicateSwitchCase.ql @@ -0,0 +1,23 @@ +/** + * @name Duplicate switch case + * @description If two cases in a switch statement have the same label, the second case + * will never be executed. + * @kind problem + * @problem.severity error + * @id go/duplicate-switch-case + * @tags maintainability + * correctness + * external/cwe/cwe-561 + * @precision very-high + */ + +import go + +/** Gets the global value number of of `e`, which is the `i`th case label of `switch`. */ +GVN switchCaseGVN(SwitchStmt switch, int i, Expr e) { + e = switch.getCase(i).getExpr(0) and result = e.getGlobalValueNumber() +} + +from SwitchStmt switch, int i, Expr e, int j, Expr f +where switchCaseGVN(switch, i, e) = switchCaseGVN(switch, j, f) and i < j +select f, "This case is a duplicate of $@.", e, "an earlier case" diff --git a/ql/src/RedundantCode/DuplicateSwitchCaseGood.go b/ql/src/RedundantCode/DuplicateSwitchCaseGood.go new file mode 100644 index 00000000..19394bd8 --- /dev/null +++ b/ql/src/RedundantCode/DuplicateSwitchCaseGood.go @@ -0,0 +1,12 @@ +package main + +func controllerGood(msg string) { + switch { + case msg == "start": + start() + case msg == "stop": + stop() + default: + panic("Message not understood.") + } +} diff --git a/ql/src/RedundantCode/ExprHasNoEffect.go b/ql/src/RedundantCode/ExprHasNoEffect.go new file mode 100644 index 00000000..3c8b85f1 --- /dev/null +++ b/ql/src/RedundantCode/ExprHasNoEffect.go @@ -0,0 +1,15 @@ +package main + +import "fmt" + +type Timestamp int + +func (t Timestamp) addDays(d int) Timestamp { + return Timestamp(int(t) + d*24*3600) +} + +func test(t Timestamp) { + fmt.Printf("Before: %s\n", t) + t.addDays(7) + fmt.Printf("After: %s\n", t) +} diff --git a/ql/src/RedundantCode/ExprHasNoEffect.qhelp b/ql/src/RedundantCode/ExprHasNoEffect.qhelp new file mode 100644 index 00000000..3b93b441 --- /dev/null +++ b/ql/src/RedundantCode/ExprHasNoEffect.qhelp @@ -0,0 +1,37 @@ + + + + +

    +An expression that has no effects (such as changing variable values or producing output) and +occurs in a context where its value is ignored possibly indicates missing code or a latent bug. +

    +
    + + +

    +Carefully inspect the expression to ensure it is not a symptom of a bug. +

    +
    + + +

    +The following example shows a named type Timestamp that is an alias for +int, representing time stamps expressed as the number of seconds elapsed since some +epoch. The addDays method returns a time stamp that is a given number of days after +another time stamp, without modifying that time stamp. +

    +

    +However, when addDays is used in function test, its result is discarded, +perhaps because the programmer mistakenly assumed that addDays updates the time stamp +in place. +

    + +

    +Instead, the result of addDays should be assigned back into t: +

    + +
    +
    diff --git a/ql/src/RedundantCode/ExprHasNoEffect.ql b/ql/src/RedundantCode/ExprHasNoEffect.ql new file mode 100644 index 00000000..95a38726 --- /dev/null +++ b/ql/src/RedundantCode/ExprHasNoEffect.ql @@ -0,0 +1,39 @@ +/** + * @name Expression has no effect + * @description An expression that has no effect and is used in a void context is most + * likely redundant and may indicate a bug. + * @kind problem + * @problem.severity warning + * @id go/useless-expression + * @tags maintainability + * correctness + * external/cwe/cwe-480 + * external/cwe/cwe-561 + * @precision very-high + */ + +import go + +/** + * Holds if `e` appears in a syntactic context where its value is discarded. + */ +predicate inVoidContext(Expr e) { + e = any(ExprStmt es).getExpr() + or + exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical)) +} + +/** + * Holds if `ce` is a call to a stub function with an empty body. + */ +predicate callToStubFunction(CallExpr ce) { + ce.getTarget().getBody().getNumStmt() = 0 +} + +from Expr e +where + not e.mayHaveOwnSideEffects() and + inVoidContext(e) and + // don't flag calls to functions with an empty body + not callToStubFunction(e) +select e, "This expression has no effect." diff --git a/ql/src/RedundantCode/ExprHasNoEffectGood.go b/ql/src/RedundantCode/ExprHasNoEffectGood.go new file mode 100644 index 00000000..1459b880 --- /dev/null +++ b/ql/src/RedundantCode/ExprHasNoEffectGood.go @@ -0,0 +1,9 @@ +package main + +import "fmt" + +func testGood(t Timestamp) { + fmt.Printf("Before: %s\n", t) + t = t.addDays(7) + fmt.Printf("After: %s\n", t) +} diff --git a/ql/src/RedundantCode/NegativeLengthCheck.go b/ql/src/RedundantCode/NegativeLengthCheck.go new file mode 100644 index 00000000..6ebdb224 --- /dev/null +++ b/ql/src/RedundantCode/NegativeLengthCheck.go @@ -0,0 +1,8 @@ +package main + +func getFirst(xs []int) int { + if len(xs) < 0 { + panic("No elements provided") + } + return xs[0] +} diff --git a/ql/src/RedundantCode/NegativeLengthCheck.qhelp b/ql/src/RedundantCode/NegativeLengthCheck.qhelp new file mode 100644 index 00000000..cff802da --- /dev/null +++ b/ql/src/RedundantCode/NegativeLengthCheck.qhelp @@ -0,0 +1,45 @@ + + + + +

    +The built-in len function returns the length of an array, slice or similar, which is +never less than zero. Hence, checking whether the result of a call to len is negative +is either redundant or indicates a logic mistake. +

    +

    +The same applies to the built-in function cap. +

    +
    + + +

    +Examine the length check to see whether it is redundant and can be removed, or a mistake that +should be fixed. +

    +
    + + +

    +The example below shows a function that returns the first element of an array, triggering a panic +if the array is empty: +

    + +

    +However, the emptiness check is ineffective: since len(xs) is never less than zero, +the condition will never hold and no panic will be triggered. Instead, the index expression +xs[0] will cause a panic. +

    +

    +The check should be rewritten like this: +

    + +
    + + +
  • Package builtin: func cap.
  • +
  • Package builtin: func len.
  • +
    +
    diff --git a/ql/src/RedundantCode/NegativeLengthCheck.ql b/ql/src/RedundantCode/NegativeLengthCheck.ql new file mode 100644 index 00000000..c199d09f --- /dev/null +++ b/ql/src/RedundantCode/NegativeLengthCheck.ql @@ -0,0 +1,36 @@ +/** + * @name Check for negative length + * @description Checking whether the result of 'len' or 'cap' is negative is pointless, + * since these functions always returns a non-negative number. + * @kind problem + * @problem.severity warning + * @precision very-high + * @id go/negative-length-check + * @tags correctness + */ + +import go + +from Comparison cmp, BuiltinFunction len, int ub, string r +where + (len = Builtin::len() or len = Builtin::cap()) and + ( + exists(RelationalComparisonExpr rel | rel = cmp | + rel.getLesserOperand() = len.getACallExpr() and + rel.getGreaterOperand().getIntValue() = ub and + ( + ub < 0 + or + ub = 0 and rel.isStrict() + ) and + r = "be less than" + ) + or + exists(EqualityTestExpr eq | eq = cmp | + eq.getAnOperand() = len.getACallExpr() and + eq.getAnOperand().getIntValue() = ub and + ub < 0 and + r = "equal" + ) + ) +select cmp, "'" + len.getName() + "' is always non-negative, and hence cannot " + r + " " + ub + "." diff --git a/ql/src/RedundantCode/NegativeLengthCheckGood.go b/ql/src/RedundantCode/NegativeLengthCheckGood.go new file mode 100644 index 00000000..0ff61b60 --- /dev/null +++ b/ql/src/RedundantCode/NegativeLengthCheckGood.go @@ -0,0 +1,8 @@ +package main + +func getFirstGood(xs []int) int { + if len(xs) == 0 { + panic("No elements provided") + } + return xs[0] +} diff --git a/ql/src/RedundantCode/RedundantExpr.go b/ql/src/RedundantCode/RedundantExpr.go new file mode 100644 index 00000000..033f3883 --- /dev/null +++ b/ql/src/RedundantCode/RedundantExpr.go @@ -0,0 +1,5 @@ +package main + +func avg(x, y float64) float64 { + return (x + x) / 2 +} diff --git a/ql/src/RedundantCode/RedundantExpr.qhelp b/ql/src/RedundantCode/RedundantExpr.qhelp new file mode 100644 index 00000000..0d79f675 --- /dev/null +++ b/ql/src/RedundantCode/RedundantExpr.qhelp @@ -0,0 +1,36 @@ + + + + +

    +Many arithmetic or logical operators yield a trivial result when applied to +identical operands: for instance, x-x is zero if x +is a number, and NaN otherwise; x&x is always +equal to x. Code like this is often the result of a typo, such as +misspelling a variable name. +

    +
    + + +

    +Carefully inspect the expression to ensure it is not a symptom of a bug. +

    +
    + + +

    +In the example below, the function avg is intended to compute the +average of two numbers x and y. However, the programmer +accidentally used x twice, so the function just returns +x: +

    + +

    +This problem can be fixed by correcting the typo: +

    + +
    + +
    diff --git a/ql/src/RedundantCode/RedundantExpr.ql b/ql/src/RedundantCode/RedundantExpr.ql new file mode 100644 index 00000000..a7edc4e7 --- /dev/null +++ b/ql/src/RedundantCode/RedundantExpr.ql @@ -0,0 +1,64 @@ +/** + * @name Identical operands + * @description Passing identical operands to an operator such as subtraction or conjunction may indicate a typo; + * even if it is intentional, it makes the code hard to read. + * @kind problem + * @problem.severity warning + * @id go/redundant-operation + * @tags correctness + * external/cwe/cwe-480 + * external/cwe/cwe-561 + * @precision very-high + */ + +import go + +/** + * Holds if `e` is a binary expression that is redundant if both operands are the same. + */ +predicate potentiallyRedundant(BinaryExpr e) { + e instanceof SubExpr + or + e instanceof DivExpr + or + e instanceof ModExpr + or + e instanceof XorExpr + or + e instanceof AndNotExpr + or + e instanceof LogicalBinaryExpr + or + e instanceof BitAndExpr + or + e instanceof BitOrExpr + or + // an expression of the form `(e + f)/2` + exists(DivExpr div | + e.(AddExpr) = div.getLeftOperand().stripParens() and + div.getRightOperand().getNumericValue() = 2 + ) +} + +/** Gets the global value number of `e`, which is the `i`th operand of `red`. */ +GVN redundantOperandGVN(BinaryExpr red, int i, Expr e) { + potentiallyRedundant(red) and + ( + i = 0 and e = red.getLeftOperand() + or + i = 1 and e = red.getRightOperand() + ) and + result = e.getGlobalValueNumber() +} + +from BinaryExpr red, Expr e, Expr f +where + redundantOperandGVN(red, 0, e) = redundantOperandGVN(red, 1, f) and + // whitelist trivial cases + not (e instanceof BasicLit and f instanceof BasicLit) and + // whitelist operations involving a symbolic constants and a literal constant; these are often feature flags + not exists(DeclaredConstant decl | + red.getAnOperand() = decl.getAReference() and + red.getAnOperand() instanceof BasicLit + ) +select red, "The $@ and $@ operand of this operation are identical.", e, "left", f, "right" diff --git a/ql/src/RedundantCode/RedundantExprGood.go b/ql/src/RedundantCode/RedundantExprGood.go new file mode 100644 index 00000000..2874ccbd --- /dev/null +++ b/ql/src/RedundantCode/RedundantExprGood.go @@ -0,0 +1,5 @@ +package main + +func avgGood(x, y float64) float64 { + return (x + y) / 2 +} diff --git a/ql/src/RedundantCode/SelfAssignment.go b/ql/src/RedundantCode/SelfAssignment.go new file mode 100644 index 00000000..ab2e585e --- /dev/null +++ b/ql/src/RedundantCode/SelfAssignment.go @@ -0,0 +1,13 @@ +package main + +type Rect struct { + x, y, width, height int +} + +func (r *Rect) setWidth(width int) { + r.width = width +} + +func (r *Rect) setHeight(height int) { + height = height +} diff --git a/ql/src/RedundantCode/SelfAssignment.qhelp b/ql/src/RedundantCode/SelfAssignment.qhelp new file mode 100644 index 00000000..33f40433 --- /dev/null +++ b/ql/src/RedundantCode/SelfAssignment.qhelp @@ -0,0 +1,41 @@ + + + + +

    +Assigning a variable to itself typically indicates a mistake such as a missing qualifier or a +misspelled variable name. +

    +
    + + +

    +Carefully inspect the assignment to check for misspellings or missing qualifiers. +

    +
    + + +

    +In the example below, the struct type Rect has two setter methods +setWidth and setHeight that are meant to be used to update the +width and height fields, respectively: +

    + +

    +Note, however, that in setHeight the programmer forgot to qualify the left-hand side +of the assignment with the receiver variable r, so the method performs a useless +assignment of the width parameter to itself and leaves the width +field unchanged. +

    +

    +To fix this issue, insert a qualifier: +

    + +
    + + +
  • The Go Programming Language Specification: Assignments.
  • +
    +
    diff --git a/ql/src/RedundantCode/SelfAssignment.ql b/ql/src/RedundantCode/SelfAssignment.ql new file mode 100644 index 00000000..fc5300ef --- /dev/null +++ b/ql/src/RedundantCode/SelfAssignment.ql @@ -0,0 +1,26 @@ +/** + * @name Self assignment + * @description Assigning a variable to itself has no effect. + * @kind problem + * @problem.severity warning + * @id go/redundant-assignment + * @tags correctness + * external/cwe/cwe-480 + * external/cwe/cwe-561 + * @precision high + */ + +import Clones + +/** + * An assignment that may be a self assignment. + */ +class PotentialSelfAssignment extends HashRoot, AssignStmt { + PotentialSelfAssignment() { getLhs().getKind() = getRhs().getKind() } +} + +from PotentialSelfAssignment assgn, HashableNode rhs +where + rhs = assgn.getRhs() and + rhs.hash() = assgn.getLhs().(HashableNode).hash() +select assgn, "This statement assigns $@ to itself.", rhs, "an expression" diff --git a/ql/src/RedundantCode/SelfAssignmentGood.go b/ql/src/RedundantCode/SelfAssignmentGood.go new file mode 100644 index 00000000..68dfc80d --- /dev/null +++ b/ql/src/RedundantCode/SelfAssignmentGood.go @@ -0,0 +1,5 @@ +package main + +func (r *Rect) setHeightGood(height int) { + r.height = height +} diff --git a/ql/src/RedundantCode/ShiftOutOfRange.go b/ql/src/RedundantCode/ShiftOutOfRange.go new file mode 100644 index 00000000..aaa05763 --- /dev/null +++ b/ql/src/RedundantCode/ShiftOutOfRange.go @@ -0,0 +1,7 @@ +package main + +func shift(base int32) int32 { + return base << 40 +} + +var x1 = shift(1) diff --git a/ql/src/RedundantCode/ShiftOutOfRange.qhelp b/ql/src/RedundantCode/ShiftOutOfRange.qhelp new file mode 100644 index 00000000..4eff2d3b --- /dev/null +++ b/ql/src/RedundantCode/ShiftOutOfRange.qhelp @@ -0,0 +1,39 @@ + + + + +

    +Shifting an integer value by more than the number of bits in its type always results in -1 for +right-shifts of negative values and 0 for other shifts. Hence, such a shift expression is either +redundant or indicates a logic mistake. +

    +
    + + +

    +Examine the length check to see whether it is redundant and can be removed, or a mistake that +should be fixed. +

    +
    + + +

    +The following code snippet attempts to compute the value 240 (1099511627776). However, +since the left operand base is of type int32 (32 bits), the shift +operation overflows, yielding zero. +

    + +

    +To prevent this, the type of base should be changed to int64: +

    + +
    + + +
  • +The Go Programming Language Specification: Arithmetic operators. +
  • +
    +
    diff --git a/ql/src/RedundantCode/ShiftOutOfRange.ql b/ql/src/RedundantCode/ShiftOutOfRange.ql new file mode 100644 index 00000000..275cae3b --- /dev/null +++ b/ql/src/RedundantCode/ShiftOutOfRange.ql @@ -0,0 +1,23 @@ +/** + * @name Shift out of range + * @description Shifting by more than the number of bits in the type of the left-hand + * side always yields 0 or -1. + * @kind problem + * @problem.severity warning + * @id go/shift-out-of-range + * @precision very-high + * @tags correctness + * external/cwe/cwe-197 + */ + +import go + +from ShiftExpr sh, IntegerType ltp, int maxsz, int amt, string atmost +where + ltp = sh.getLeftOperand().getType() and + (if exists(ltp.getSize()) then atmost = "" else atmost = "(at most) ") and + maxsz = max(ltp.getASize()) and + amt = sh.getRightOperand().getIntValue() and + not ltp.getASize() > amt +select sh, + "Shifting a value of " + atmost + maxsz + " bits by " + amt + " always yields either 0 or -1." diff --git a/ql/src/RedundantCode/ShiftOutOfRangeGood.go b/ql/src/RedundantCode/ShiftOutOfRangeGood.go new file mode 100644 index 00000000..f43f80c6 --- /dev/null +++ b/ql/src/RedundantCode/ShiftOutOfRangeGood.go @@ -0,0 +1,7 @@ +package main + +func shiftGood(base int64) int64 { + return base << 40 +} + +var x2 = shiftGood(1) diff --git a/ql/src/RedundantCode/UnreachableStatement.go b/ql/src/RedundantCode/UnreachableStatement.go new file mode 100644 index 00000000..10250238 --- /dev/null +++ b/ql/src/RedundantCode/UnreachableStatement.go @@ -0,0 +1,13 @@ +package main + +func mul(xs []int) int { + res := 1 + for i := 0; i < len(xs); i++ { + x := xs[i] + res *= x + if res == 0 { + } + return 0 + } + return res +} diff --git a/ql/src/RedundantCode/UnreachableStatement.qhelp b/ql/src/RedundantCode/UnreachableStatement.qhelp new file mode 100644 index 00000000..ae3feadf --- /dev/null +++ b/ql/src/RedundantCode/UnreachableStatement.qhelp @@ -0,0 +1,36 @@ + + + + +

    +An unreachable statement often indicates missing code or a latent bug and should be examined +carefully. +

    +
    + + +

    +Examine the surrounding code to determine why the statement has become unreachable. If it is no +longer needed, remove the statement. +

    +
    + + +

    +In the following example, the body of the for statement cannot terminate normally, +so the update statement i++ becomes unreachable: +

    + +

    +Most likely, the return statement should be moved inside the if +statement: +

    + +
    + + +
  • Wikipedia: Unreachable code.
  • +
    +
    diff --git a/ql/src/RedundantCode/UnreachableStatement.ql b/ql/src/RedundantCode/UnreachableStatement.ql new file mode 100644 index 00000000..639fafa6 --- /dev/null +++ b/ql/src/RedundantCode/UnreachableStatement.ql @@ -0,0 +1,53 @@ +/** + * @name Unreachable statement + * @description Unreachable statements are often indicative of missing code or latent bugs + * and should be avoided. + * @kind problem + * @problem.severity warning + * @id go/unreachable-statement + * @tags maintainability + * correctness + * external/cwe/cwe-561 + * @precision very-high + */ + +import go + +ControlFlow::Node nonGuardPredecessor(ControlFlow::Node nd) { + exists(ControlFlow::Node pred | pred = nd.getAPredecessor() | + if pred instanceof ControlFlow::ConditionGuardNode + then result = nonGuardPredecessor(pred) + else result = pred + ) +} + +predicate whitelist(Stmt s) { + // `panic("unreachable")` and similar + exists(CallExpr ce | ce = s.(ExprStmt).getExpr() or ce = s.(ReturnStmt).getExpr() | + ce.getTarget().mustPanic() or ce.getCalleeName().toLowerCase() = "error" + ) + or + // `return nil` and similar + exists(ReturnStmt ret | ret = s | + forall(Expr retval | retval = ret.getAnExpr() | + retval = Builtin::nil().getAReference() or + retval instanceof BasicLit or + retval.(UnaryExpr).getOperand() instanceof BasicLit + ) + ) + or + // statements in an `if false { ... }` and similar + exists(IfStmt is, ControlFlow::ConditionGuardNode iffalse, Expr cond, boolean b | + iffalse.getCondition() = is.getCond() and + iffalse = s.getFirstControlFlowNode().getAPredecessor() and + cond.getBoolValue() = b and + iffalse.ensures(DataFlow::exprNode(cond), b.booleanNot()) + ) +} + +from Stmt s, ControlFlow::Node fst +where + fst = s.getFirstControlFlowNode() and + not exists(nonGuardPredecessor(fst)) and + not whitelist(s) +select s, "This statement is unreachable." diff --git a/ql/src/RedundantCode/UnreachableStatementGood.go b/ql/src/RedundantCode/UnreachableStatementGood.go new file mode 100644 index 00000000..8d959516 --- /dev/null +++ b/ql/src/RedundantCode/UnreachableStatementGood.go @@ -0,0 +1,13 @@ +package main + +func mulGood(xs []int) int { + res := 1 + for i := 0; i < len(xs); i++ { + x := xs[i] + res *= x + if res == 0 { + return 0 + } + } + return res +} diff --git a/ql/src/Security/CWE-020/IncompleteHostnameRegexp.go b/ql/src/Security/CWE-020/IncompleteHostnameRegexp.go new file mode 100644 index 00000000..cccf148c --- /dev/null +++ b/ql/src/Security/CWE-020/IncompleteHostnameRegexp.go @@ -0,0 +1,16 @@ +package main + +import ( + "errors" + "regexp" + "net/http" +) + +func checkRedirect(req *http.Request, via []*http.Request) error { + // BAD: the host of `url` may be controlled by an attacker + re := "^((www|beta).)?example.com/" + if matched, _ := regexp.MatchString(re, req.URL.Host); matched { + return nil + } + return errors.New("Invalid redirect") +} diff --git a/ql/src/Security/CWE-020/IncompleteHostnameRegexp.qhelp b/ql/src/Security/CWE-020/IncompleteHostnameRegexp.qhelp new file mode 100644 index 00000000..cf4655db --- /dev/null +++ b/ql/src/Security/CWE-020/IncompleteHostnameRegexp.qhelp @@ -0,0 +1,50 @@ + + + + +

    +Sanitizing untrusted URLs is an important technique for preventing attacks such as request +forgeries and malicious redirections. Often, this is done by checking that the host of a URL +is in a set of allowed hosts. +

    +

    +If a regular expression implements such a check, it is easy to accidentally make the check too +permissive by not escaping regular-expression meta-characters such as .. +

    +

    +Even if the check is not used in a security-critical context, the incomplete check may still cause +undesirable behavior when it accidentally succeeds. +

    +
    + + +

    +Escape all meta-characters appropriately when constructing regular expressions for security checks, +paying special attention to the . meta-character. +

    +
    + + +

    +The following example code checks that a URL redirection will reach the example.com +domain, or one of its subdomains. +

    + +

    +The check is however easy to bypass because the unescaped . allows for any character +before example.com, effectively allowing the redirect to go to an attacker-controlled +domain such as wwwXexample.com. +

    +

    +Address this vulnerability by escaping . appropriately: +

    + +
    + + +
  • OWASP: SSRF
  • +
  • OWASP: Unvalidated Redirects and Forwards Cheat Sheet.
  • +
    +
    diff --git a/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql b/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql new file mode 100644 index 00000000..7a8559f0 --- /dev/null +++ b/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql @@ -0,0 +1,56 @@ +/** + * @name Incomplete regular expression for hostnames + * @description Matching a URL or hostname against a regular expression that contains an unescaped + * dot as part of the hostname might match more hostnames than expected. + * @kind path-problem + * @problem.severity warning + * @precision high + * @id go/incomplete-hostname-regexp + * @tags correctness + * security + * external/cwe/cwe-20 + */ + +import go +import DataFlow::PathGraph + +/** + * Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`, + * and `pattern` contains a subtle mistake that allows it to match unexpected hosts. + */ +bindingset[pattern] +predicate isIncompleteHostNameRegexpPattern(string pattern, string hostPart) { + hostPart = pattern + .regexpCapture("(?i).*" + + // an unescaped single `.` + "(? + + + +

    +Sanitizing untrusted input with regular expressions is a common technique. However, it is +error-prone to match untrusted input against regular expressions without anchors such as +^ or $. Malicious input can bypass such security checks by embedding +one of the allowed patterns in an unexpected location. +

    +

    +Even if the matching is not done in a security-critical context, it may still cause undesirable +behavior when the regular expression accidentally matches. +

    +
    + + +

    +Use anchors to ensure that regular expressions match at the expected locations. +

    +
    + + +

    +The following example code checks that a URL redirection will reach the example.com +domain, or one of its subdomains, and not some malicious site. +

    + +

    +The check with the regular expression match is, however, easy to bypass. For example, the string +http://example.com/ can be embedded in the query string component: +http://evil-example.net/?x=http://example.com/. +

    +

    +Address these shortcomings by using anchors in the regular expression instead: +

    + +

    +A related mistake is to write a regular expression with multiple alternatives, but to only anchor +one of the alternatives. As an example, the regular expression +^www\.example\.com|beta\.example\.com will match the host +evil.beta.example.com because the regular expression is parsed as +(^www\.example\.com)|(beta\.example\.com)/, so the second alternative +beta\.example\.com is not anchored at the beginning of the string. +

    +
    + + +
  • OWASP: SSRF
  • +
  • OWASP: Unvalidated Redirects and Forwards Cheat Sheet.
  • +
    +
    diff --git a/ql/src/Security/CWE-020/MissingRegexpAnchor.ql b/ql/src/Security/CWE-020/MissingRegexpAnchor.ql new file mode 100644 index 00000000..5ff9eaf9 --- /dev/null +++ b/ql/src/Security/CWE-020/MissingRegexpAnchor.ql @@ -0,0 +1,77 @@ +/** + * @name Missing regular expression anchor + * @description Regular expressions without anchors can be vulnerable to bypassing. + * @kind problem + * @problem.severity warning + * @precision high + * @id go/regex/missing-regexp-anchor + * @tags correctness + * security + * external/cwe/cwe-20 + */ + +import go + +/** + * Holds if `src` is a pattern for a collection of alternatives where + * only the first or last alternative is anchored, indicating a + * precedence mistake explained by `msg`. + * + * The canonical example of such a mistake is: `^a|b|c`, which is + * parsed as `(^a)|(b)|(c)`. + */ +bindingset[re] +predicate isInterestingSemiAnchoredRegexpString(string re, string msg) { + exists(string str, string maybeGroupedStr, string regex, string anchorPart, string escapedDot | + // a dot that might be escaped in a regular expression, for example `regexp.Compile("\\.")` + escapedDot = "\\\\[.]" and + // a string that is mostly free from special reqular expression symbols + str = "(?:(?:" + escapedDot + ")|[a-z:/.?_,@0-9 -])+" and + // the string may be wrapped in parentheses + maybeGroupedStr = "(?:" + str + "|\\(" + str + "\\))" and + ( + // a problematic pattern: `^a|b|...|x` + regex = "(?i)(\\^" + maybeGroupedStr + ")(?:\\|" + maybeGroupedStr + ")+" + or + // a problematic pattern: `a|b|...|x$` + regex = "(?i)(?:" + maybeGroupedStr + "\\|)+(" + maybeGroupedStr + "\\$)" + ) and + anchorPart = re.regexpCapture(regex, 1) and + anchorPart.regexpMatch("(?i).*[a-z].*") and + msg = "Misleading operator precedence. The subexpression '" + anchorPart + + "' is anchored, but the other parts of this regular expression are not." + ) +} + +/** + * Holds if `src` is an unanchored pattern for a URL, indicating a + * mistake explained by `msg`. + */ +bindingset[re] +predicate isInterestingUnanchoredRegexpString(string re, string msg) { + // a substring sequence of a protocol and subdomains, perhaps with some regex characters mixed in, followed by a known TLD + re.regexpMatch("(?i)[():|?a-z0-9-\\\\./]+[.]" + commonTLD() + "([/#?():]\\S*)?") and + // without any anchors + re.regexpMatch("[^$^]+") and + msg = "When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it." +} + +class Config extends DataFlow::Configuration { + Config() { this = "MissingRegexpAnchor::Config" } + + predicate isSource(DataFlow::Node source, string msg) { + exists(Expr e | e = source.asExpr() | + isInterestingUnanchoredRegexpString(e.getStringValue(), msg) + or + isInterestingSemiAnchoredRegexpString(e.getStringValue(), msg) + ) + } + + override predicate isSource(DataFlow::Node source) { isSource(source, _) } + + override predicate isSink(DataFlow::Node sink) { sink instanceof RegexpPattern } +} + +from Config c, DataFlow::PathNode source, string msg +where c.hasFlowPath(source, _) and c.isSource(source.getNode(), msg) +select source.getNode(), msg diff --git a/ql/src/Security/CWE-020/MissingRegexpAnchorGood.go b/ql/src/Security/CWE-020/MissingRegexpAnchorGood.go new file mode 100644 index 00000000..063e0353 --- /dev/null +++ b/ql/src/Security/CWE-020/MissingRegexpAnchorGood.go @@ -0,0 +1,16 @@ +package main + +import ( + "errors" + "net/http" + "regexp" +) + +func checkRedirect2Good(req *http.Request, via []*http.Request) error { + // GOOD: the host of `req.URL` cannot be controlled by an attacker + re := "^https?://www\\.example\\.com/" + if matched, _ := regexp.MatchString(re, req.URL.String()); matched { + return nil + } + return errors.New("Invalid redirect") +} diff --git a/ql/src/Security/CWE-022/TaintedPath.go b/ql/src/Security/CWE-022/TaintedPath.go new file mode 100644 index 00000000..3b6df910 --- /dev/null +++ b/ql/src/Security/CWE-022/TaintedPath.go @@ -0,0 +1,19 @@ +package main + +import ( + "io/ioutil" + "net/http" + "path/filepath" +) + +func handler(w http.ResponseWriter, r *http.Request) { + path := r.URL.Query()["path"][0] + + // BAD: This could read any file on the file system + data, _ := ioutil.ReadFile(path) + w.Write(data) + + // BAD: This could still read any file on the file system + data, _ = ioutil.ReadFile(filepath.Join("/home/user/", path)) + w.Write(data) +} diff --git a/ql/src/Security/CWE-022/TaintedPath.qhelp b/ql/src/Security/CWE-022/TaintedPath.qhelp new file mode 100644 index 00000000..6004e9c3 --- /dev/null +++ b/ql/src/Security/CWE-022/TaintedPath.qhelp @@ -0,0 +1,51 @@ + + + + +

    +Accessing files using paths constructed from user-controlled data can allow an attacker to access +unexpected resources. This can result in sensitive information being revealed or deleted, or an +attacker being able to influence behavior by modifying unexpected files. +

    +
    + + +

    +Validate user input before using it to construct a file path, either using an off-the-shelf library +or by performing custom validation. +

    +

    +Ideally, follow these rules: +

    +
      +
    • Do not allow more than a single "." character.
    • +
    • Do not allow directory separators such as "/" or "\" (depending on the file system).
    • +
    • Do not rely on simply replacing problematic sequences such as "../". For example, after +applying this filter to ".../...//", the resulting string would still be "../".
    • +
    • Use a whitelist of known good patterns.
    • +
    +
    + + +

    +In the first example, a file name is read from an HTTP request and then used to access a file. +However, a malicious user could enter a file name which is an absolute path, such as +"/etc/passwd". +

    +

    +In the second example, it appears that the user is restricted to opening a file within the +"user" home directory. However, a malicious user could enter a file name containing +special characters. For example, the string "../../etc/passwd" will result in the code +reading the file located at "/home/user/../../etc/passwd", which is the system's +password file. This file would then be sent back to the user, giving them access to password +information. +

    + +
    + + +
  • OWASP: Path Traversal.
  • +
    +
    diff --git a/ql/src/Security/CWE-022/TaintedPath.ql b/ql/src/Security/CWE-022/TaintedPath.ql new file mode 100644 index 00000000..5504a674 --- /dev/null +++ b/ql/src/Security/CWE-022/TaintedPath.ql @@ -0,0 +1,24 @@ +/** + * @name Uncontrolled data used in path expression + * @description Accessing paths influenced by users can allow an attacker to access + * unexpected resources. + * @kind path-problem + * @problem.severity error + * @precision high + * @id go/path-injection + * @tags security + * external/cwe/cwe-022 + * external/cwe/cwe-023 + * external/cwe/cwe-036 + * external/cwe/cwe-073 + * external/cwe/cwe-099 + */ + +import go +import semmle.go.security.TaintedPath::TaintedPath +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "This path depends on $@.", source.getNode(), + "a user-provided value" diff --git a/ql/src/Security/CWE-022/ZipSlip.go b/ql/src/Security/CWE-022/ZipSlip.go new file mode 100644 index 00000000..1628eabb --- /dev/null +++ b/ql/src/Security/CWE-022/ZipSlip.go @@ -0,0 +1,16 @@ +package main + +import ( + "archive/zip" + "io/ioutil" + "path/filepath" +) + +func unzip(f string) { + r, _ := zip.OpenReader(f) + for _, f := range r.File { + p, _ := filepath.Abs(f.Name) + // BAD: This could overwrite any file on the file system + ioutil.WriteFile(p, []byte("present"), 0666) + } +} diff --git a/ql/src/Security/CWE-022/ZipSlip.qhelp b/ql/src/Security/CWE-022/ZipSlip.qhelp new file mode 100644 index 00000000..dac4dc8a --- /dev/null +++ b/ql/src/Security/CWE-022/ZipSlip.qhelp @@ -0,0 +1,69 @@ + + + + +

    +Extracting files from a malicious zip archive without validating that the destination file path +is within the destination directory can cause files outside the destination directory to be +overwritten, due to the possible presence of directory traversal elements (..) in +archive paths. +

    + +

    +Zip archives contain archive entries representing each file in the archive. These entries +include a file path for the entry, but these file paths are not restricted and may contain +unexpected special elements such as the directory traversal element (..). If these +file paths are used to determine which output file the contents of an archive item should be written to, then +the file may be written to an unexpected location. This can result in sensitive information being +revealed or deleted, or an attacker being able to influence behavior by modifying unexpected +files. +

    + +

    +For example, if a zip file contains a file entry ..\sneaky-file, and the zip file +is extracted to the directory c:\output, then naively combining the paths would result +in an output file path of c:\output\..\sneaky-file, which would cause the file to be +written to c:\sneaky-file. +

    +
    + + +

    +Ensure that output paths constructed from zip archive entries are validated +to prevent writing files to unexpected locations. +

    + +

    +The recommended way of writing an output file from a zip archive entry is to check that +".." does not occur in the path. +

    +
    + + +

    +In this example an archive is extracted without validating file paths. +If archive.zip contained relative paths (for +instance, if it were created by something like zip archive.zip +../file.txt) then executing this code could write to locations +outside the destination directory. +

    + +

    To fix this vulnerability, we need to check that the path does not +contain any ".." elements in it. +

    + +
    + + +
  • +Snyk: +Zip Slip Vulnerability. +
  • +
  • +OWASP: +Path Traversal. +
  • +
    +
    diff --git a/ql/src/Security/CWE-022/ZipSlip.ql b/ql/src/Security/CWE-022/ZipSlip.ql new file mode 100644 index 00000000..3e087f94 --- /dev/null +++ b/ql/src/Security/CWE-022/ZipSlip.ql @@ -0,0 +1,22 @@ +/** + * @name Arbitrary file write during zip extraction ("zip slip") + * @description Extracting files from a malicious zip archive without validating that the + * destination file path is within the destination directory can cause files outside + * the destination directory to be overwritten. + * @kind path-problem + * @id go/zipslip + * @problem.severity error + * @precision high + * @tags security + * external/cwe/cwe-022 + */ + +import go +import semmle.go.security.ZipSlip::ZipSlip +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select source.getNode(), source, sink, + "Unsanitized archive entry, which may contain '..', is used in a $@.", sink.getNode(), + "file system operation" diff --git a/ql/src/Security/CWE-022/ZipSlipGood.go b/ql/src/Security/CWE-022/ZipSlipGood.go new file mode 100644 index 00000000..40af79fb --- /dev/null +++ b/ql/src/Security/CWE-022/ZipSlipGood.go @@ -0,0 +1,19 @@ +package main + +import ( + "archive/zip" + "io/ioutil" + "path/filepath" + "strings" +) + +func unzipGood(f string) { + r, _ := zip.OpenReader(f) + for _, f := range r.File { + p, _ := filepath.Abs(f.Name) + // GOOD: Check that path does not contain ".." before using it + if !strings.Contains(p, "..") { + ioutil.WriteFile(p, []byte("present"), 0666) + } + } +} diff --git a/ql/src/Security/CWE-078/CommandInjection.go b/ql/src/Security/CWE-078/CommandInjection.go new file mode 100644 index 00000000..ff046f24 --- /dev/null +++ b/ql/src/Security/CWE-078/CommandInjection.go @@ -0,0 +1,12 @@ +package main + +import ( + "net/http" + "os/exec" +) + +func handler(req *http.Request) { + cmdName := req.URL.Query()["cmd"][0] + cmd := exec.Command(cmdName) + cmd.Run() +} diff --git a/ql/src/Security/CWE-078/CommandInjection.qhelp b/ql/src/Security/CWE-078/CommandInjection.qhelp new file mode 100644 index 00000000..0b946256 --- /dev/null +++ b/ql/src/Security/CWE-078/CommandInjection.qhelp @@ -0,0 +1,41 @@ + + + + +

    +If a system command invocation is built from user-provided data without sufficient sanitization, +a malicious user may be able to run commands to exfiltrate data or compromise the system. +

    +
    + + +

    +If possible, use hard-coded string literals to specify the command to run. Instead of interpreting +user input directly as command names, examine the input and then choose among hard-coded string +literals. +

    +

    +If this is not possible, then add sanitization code to verify that the user input is safe before +using it. +

    +
    + + +

    +In the following example, assume the function handler is an HTTP request handler in a +web application, whose parameter req contains the request object: +

    + +

    +The handler extracts the name of a system command from the request object, and then runs it without +any further checks, which can cause a command-injection vulnerability. +

    +
    + +
  • +OWASP: Command Injection. +
  • +
    +
    diff --git a/ql/src/Security/CWE-078/CommandInjection.ql b/ql/src/Security/CWE-078/CommandInjection.ql new file mode 100644 index 00000000..aea01ed4 --- /dev/null +++ b/ql/src/Security/CWE-078/CommandInjection.ql @@ -0,0 +1,20 @@ +/** + * @name Command built from user-controlled sources + * @description Building a system command from user-controlled sources is vulnerable to insertion of + * malicious code by the user. + * @kind path-problem + * @problem.severity error + * @precision high + * @id go/command-injection + * @tags security + * external/cwe/cwe-078 + */ + +import go +import semmle.go.security.CommandInjection +import DataFlow::PathGraph + +from CommandInjection::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "This command depends on $@.", source.getNode(), + "a user-provided value" diff --git a/ql/src/Security/CWE-079/ReflectedXss.go b/ql/src/Security/CWE-079/ReflectedXss.go new file mode 100644 index 00000000..3f976dc7 --- /dev/null +++ b/ql/src/Security/CWE-079/ReflectedXss.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "net/http" +) + +func serve() { + http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + username := r.Form.Get("username") + if !isValidUsername(username) { + // BAD: a request parameter is incorporated without validation into the response + fmt.Fprintf(w, "Unknown user: %q", username) + } else { + // TODO: do something exciting + } + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/src/Security/CWE-079/ReflectedXss.qhelp b/ql/src/Security/CWE-079/ReflectedXss.qhelp new file mode 100644 index 00000000..038c7646 --- /dev/null +++ b/ql/src/Security/CWE-079/ReflectedXss.qhelp @@ -0,0 +1,52 @@ + + + + +

    +Directly writing user input (for example, an HTTP request parameter) to an HTTP response +without properly sanitizing the input first, allows for a cross-site scripting vulnerability. +

    +

    +This kind of vulnerability is also called reflected cross-site scripting, to distinguish +it from other types of cross-site scripting. +

    +
    + + +

    +To guard against cross-site scripting, consider using contextual output encoding/escaping before +writing user input to the response, or one of the other solutions that are mentioned in the +references. +

    +
    + + +

    +The following example code writes part of an HTTP request (which is controlled by the user) +directly to the response. This leaves the website vulnerable to cross-site scripting. +

    + +

    +Sanitizing the user-controlled data prevents the vulnerability: +

    + +
    + + +
  • +OWASP: +XSS +(Cross Site Scripting) Prevention Cheat Sheet. +
  • +
  • +OWASP +Types of Cross-Site +Scripting. +
  • +
  • +Wikipedia: Cross-site scripting. +
  • +
    +
    diff --git a/ql/src/Security/CWE-079/ReflectedXss.ql b/ql/src/Security/CWE-079/ReflectedXss.ql new file mode 100644 index 00000000..bc11cbc7 --- /dev/null +++ b/ql/src/Security/CWE-079/ReflectedXss.ql @@ -0,0 +1,21 @@ +/** + * @name Reflected cross-site scripting + * @description Writing user input directly to an HTTP response allows for + * a cross-site scripting vulnerability. + * @kind path-problem + * @problem.severity error + * @precision high + * @id go/reflected-xss + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-116 + */ + +import go +import semmle.go.security.ReflectedXss::ReflectedXss +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.", + source.getNode(), "user-provided value" diff --git a/ql/src/Security/CWE-079/ReflectedXssGood.go b/ql/src/Security/CWE-079/ReflectedXssGood.go new file mode 100644 index 00000000..19408bcf --- /dev/null +++ b/ql/src/Security/CWE-079/ReflectedXssGood.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "html" + "net/http" +) + +func serve1() { + http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + username := r.Form.Get("username") + if !isValidUsername(username) { + // BAD: a request parameter is incorporated without validation into the response + fmt.Fprintf(w, "Unknown user: %q", html.EscapeString(username)) + } else { + // TODO: do something exciting + } + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/src/Security/CWE-089/SqlInjection.go b/ql/src/Security/CWE-089/SqlInjection.go new file mode 100644 index 00000000..0df976d9 --- /dev/null +++ b/ql/src/Security/CWE-089/SqlInjection.go @@ -0,0 +1,13 @@ +package main + +import ( + "database/sql" + "fmt" + "net/http" +) + +func handler(db *sql.DB, req *http.Request) { + q := fmt.Sprintf("SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='%s' ORDER BY PRICE", + req.URL.Query()["category"]) + db.Query(q) +} diff --git a/ql/src/Security/CWE-089/SqlInjection.qhelp b/ql/src/Security/CWE-089/SqlInjection.qhelp new file mode 100644 index 00000000..8293fbeb --- /dev/null +++ b/ql/src/Security/CWE-089/SqlInjection.qhelp @@ -0,0 +1,43 @@ + + + + +

    +If a database query (such as an SQL or NoSQL query) is built from user-provided data without +sufficient sanitization, a malicious user may be able to run commands that exfiltrate, tamper with, +or destroy data stored in the database. +

    +
    + + +

    +Most database connector libraries offer a way of safely embedding untrusted data into a query by +means of query parameters or prepared statements. Use these features rather than building queries +by string concatenation. +

    +
    + + +

    +In the following example, assume the function handler is an HTTP request handler in a +web application, whose parameter req contains the request object: +

    + +

    +The handler constructs an SQL query involving user input taken from the request object unsafely +using fmt.Sprintf to embed a request parameter directly into the query string +q. The parameter may include quote characters, allowing a malicious user to terminate +the string literal into which the parameter is embedded and add arbitrary SQL code after it. +

    +

    +Instead, the untrusted query parameter should be safely embedded using placeholder parameters: +

    + +
    + + +
  • Wikipedia: SQL injection.
  • +
    +
    diff --git a/ql/src/Security/CWE-089/SqlInjection.ql b/ql/src/Security/CWE-089/SqlInjection.ql new file mode 100644 index 00000000..d0af6ec7 --- /dev/null +++ b/ql/src/Security/CWE-089/SqlInjection.ql @@ -0,0 +1,20 @@ +/** + * @name Database query built from user-controlled sources + * @description Building a database query from user-controlled sources is vulnerable to insertion of + * malicious code by the user. + * @kind path-problem + * @problem.severity error + * @precision high + * @id go/sql-injection + * @tags security + * external/cwe/cwe-089 + */ + +import go +import semmle.go.security.SqlInjection +import DataFlow::PathGraph + +from SqlInjection::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "This query depends on $@.", source.getNode(), + "a user-provided value" diff --git a/ql/src/Security/CWE-089/SqlInjectionGood.go b/ql/src/Security/CWE-089/SqlInjectionGood.go new file mode 100644 index 00000000..8c9ea861 --- /dev/null +++ b/ql/src/Security/CWE-089/SqlInjectionGood.go @@ -0,0 +1,11 @@ +package main + +import ( + "database/sql" + "net/http" +) + +func handlerGood(db *sql.DB, req *http.Request) { + q := "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='?' ORDER BY PRICE" + db.Query(q, req.URL.Query()["category"]) +} diff --git a/ql/src/Security/CWE-312/CleartextLogging.go b/ql/src/Security/CWE-312/CleartextLogging.go new file mode 100644 index 00000000..29f6a6ef --- /dev/null +++ b/ql/src/Security/CWE-312/CleartextLogging.go @@ -0,0 +1,17 @@ +package main + +import ( + "log" + "net/http" +) + +func serve() { + http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + user := r.Form.Get("user") + pw := r.Form.Get("password") + + log.Printf("Registering new user %s with password %s.\n", user, pw) + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/src/Security/CWE-312/CleartextLogging.qhelp b/ql/src/Security/CWE-312/CleartextLogging.qhelp new file mode 100644 index 00000000..e8326e59 --- /dev/null +++ b/ql/src/Security/CWE-312/CleartextLogging.qhelp @@ -0,0 +1,50 @@ + + + + +

    +Sensitive information that is logged unencrypted is accessible to an attacker +who gains access to the logs. +

    +
    + + +

    +Ensure that sensitive information is always encrypted or obfuscated before being +logged. +

    + +

    +In general, decrypt sensitive information only at the point where it is +necessary for it to be used in cleartext. +

    + +

    +Be aware that external processes often store the standard out and +standard error streams of the application, causing logged sensitive +information to be stored. +

    +
    + + +

    +The following example code logs user credentials (in this case, their password) +in plain text: +

    + +

    +Instead, the credentials should be encrypted, obfuscated, or omitted entirely: +

    + +
    + + + +
  • M. Dowd, J. McDonald and J. Schuhm, The Art of Software Security Assessment, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.
  • +
  • M. Howard and D. LeBlanc, Writing Secure Code, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.
  • +
  • OWASP: Password Plaintext Storage.
  • + +
    +
    diff --git a/ql/src/Security/CWE-312/CleartextLogging.ql b/ql/src/Security/CWE-312/CleartextLogging.ql new file mode 100644 index 00000000..62305984 --- /dev/null +++ b/ql/src/Security/CWE-312/CleartextLogging.ql @@ -0,0 +1,22 @@ +/** + * @name Clear-text logging of sensitive information + * @description Logging sensitive information without encryption or hashing can + * expose it to an attacker. + * @kind path-problem + * @problem.severity error + * @precision high + * @id go/clear-text-logging + * @tags security + * external/cwe/cwe-312 + * external/cwe/cwe-315 + * external/cwe/cwe-359 + */ + +import go +import semmle.go.security.CleartextLogging::CleartextLogging +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Sensitive data returned by $@ is logged here.", + source.getNode(), source.getNode().(Source).describe() diff --git a/ql/src/Security/CWE-312/CleartextLoggingGood.go b/ql/src/Security/CWE-312/CleartextLoggingGood.go new file mode 100644 index 00000000..3b4e1937 --- /dev/null +++ b/ql/src/Security/CWE-312/CleartextLoggingGood.go @@ -0,0 +1,19 @@ +// +build ignore + +package main + +import ( + "log" + "net/http" +) + +func serve1() { + http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + user := r.Form.Get("user") + pw := r.Form.Get("password") + + log.Printf("Registering new user %s.\n", user) + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/src/Security/CWE-601/OpenUrlRedirect.go b/ql/src/Security/CWE-601/OpenUrlRedirect.go new file mode 100644 index 00000000..606b5d43 --- /dev/null +++ b/ql/src/Security/CWE-601/OpenUrlRedirect.go @@ -0,0 +1,12 @@ +package main + +import ( + "net/http" +) + +func serve() { + http.HandleFunc("/redir", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + http.Redirect(w, r, r.Form.Get("target"), 302) + }) +} diff --git a/ql/src/Security/CWE-601/OpenUrlRedirect.qhelp b/ql/src/Security/CWE-601/OpenUrlRedirect.qhelp new file mode 100644 index 00000000..11916c48 --- /dev/null +++ b/ql/src/Security/CWE-601/OpenUrlRedirect.qhelp @@ -0,0 +1,42 @@ + + + + +

    +Directly incorporating user input into a URL redirect request without validating the input can +facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a malicious +site that looks very similar to the real site they intend to visit, but is controlled by the +attacker. +

    +
    + + +

    +To guard against untrusted URL redirection, it is advisable to avoid putting user input directly into +a redirect URL. Instead, maintain a list of authorized redirects on the server; then choose from +that list based on the user input provided. +

    +
    + + +

    +The following example shows an HTTP request parameter being used directly in a URL redirect without +validating the input, which facilitates phishing attacks: +

    + + + +

    +One way to remedy the problem is to validate the user input against a known fixed string +before doing the redirection: +

    + + +
    + + +
  • OWASP: + XSS Unvalidated Redirects and Forwards Cheat Sheet.
  • +
    + +
    diff --git a/ql/src/Security/CWE-601/OpenUrlRedirect.ql b/ql/src/Security/CWE-601/OpenUrlRedirect.ql new file mode 100644 index 00000000..8ef2d574 --- /dev/null +++ b/ql/src/Security/CWE-601/OpenUrlRedirect.ql @@ -0,0 +1,20 @@ +/** + * @name Open URL redirect + * @description Open URL redirection based on unvalidated user input + * may cause redirection to malicious web sites. + * @kind path-problem + * @problem.severity warning + * @id go/unvalidated-url-redirection + * @tags security + * external/cwe/cwe-601 + * @precision high + */ + +import go +import semmle.go.security.OpenUrlRedirect::OpenUrlRedirect +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(), + "user-provided value" diff --git a/ql/src/Security/CWE-601/OpenUrlRedirectGood.go b/ql/src/Security/CWE-601/OpenUrlRedirectGood.go new file mode 100644 index 00000000..7aa9e4ae --- /dev/null +++ b/ql/src/Security/CWE-601/OpenUrlRedirectGood.go @@ -0,0 +1,23 @@ +package main + +import ( + "net/http" + "net/url" +) + +func serve() { + http.HandleFunc("/redir", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + target, err := url.Parse(r.Form.Get("target")) + if err != nil { + // ... + } + + if target.Hostname() == "semmle.com" { + // GOOD: checking hostname + http.Redirect(w, r, target.String(), 302) + } else { + http.WriteHeader(400) + } + }) +} diff --git a/ql/src/Security/CWE-798/HardcodedCredentials.go b/ql/src/Security/CWE-798/HardcodedCredentials.go new file mode 100644 index 00000000..de31e5b1 --- /dev/null +++ b/ql/src/Security/CWE-798/HardcodedCredentials.go @@ -0,0 +1,24 @@ +// +build ignore + +package main + +import ( + "database/sql" + "fmt" + + _ "github.com/lib/pq" +) + +const ( + user = "dbuser" + password = "secretpassword" +) + +func connect() *sql.DB { + connStr := fmt.Sprintf("postgres://%s:%s@localhost/pqgotest", user, password) + db, err := sql.Open("postgres", connStr) + if err != nil { + return nil + } + return db +} diff --git a/ql/src/Security/CWE-798/HardcodedCredentials.qhelp b/ql/src/Security/CWE-798/HardcodedCredentials.qhelp new file mode 100644 index 00000000..89b61984 --- /dev/null +++ b/ql/src/Security/CWE-798/HardcodedCredentials.qhelp @@ -0,0 +1,45 @@ + + + + +

    + Including unencrypted hard-coded authentication credentials in source code is dangerous because + the credentials may be easily discovered. For example, the code may be open source, or it may + be leaked or accidentally revealed, making the credentials visible to an attacker. This, in turn, + might enable them to gain unauthorized access, or to obtain privileged information. +

    +
    + + +

    + Remove hard-coded credentials, such as user names, passwords and certificates, from source code. + Instead, place them in configuration files, environment variables or other data stores if necessary. + If possible, store configuration files including credential data separately from the source code, + in a secure location with restricted access. +

    +
    + + +

    + The following code example connects to a Postgres database using the lib/pq package + and hard-codes user name and password: +

    + + + +

    + Instead, user name and password can be supplied through the environment variables + PGUSER and PGPASSWORD, which can be set externally without hard-coding + credentials in the source code. +

    +
    + + +
  • +OWASP: +Use of hard-coded password. +
  • +
    +
    diff --git a/ql/src/Security/CWE-798/HardcodedCredentials.ql b/ql/src/Security/CWE-798/HardcodedCredentials.ql new file mode 100644 index 00000000..082ee37b --- /dev/null +++ b/ql/src/Security/CWE-798/HardcodedCredentials.ql @@ -0,0 +1,57 @@ +/** + * @name Hard-coded credentials + * @description Hard-coding credentials in source code may enable an attacker + * to gain unauthorized access. + * @kind problem + * @problem.severity warning + * @precision high + * @id go/hardcoded-credentials + * @tags security + * external/cwe/cwe-259 + * external/cwe/cwe-321 + * external/cwe/cwe-798 + */ + +import go + +/** + * Holds if `sink` is used in a context that suggests it may hold sensitive data of + * the given `type`. + */ +predicate isSensitive(DataFlow::Node sink, string type) { + exists(Write write, string name | + write.getRhs() = sink and + name = write.getLhs().getName() and + // whitelist obvious test password variables + not name.regexpMatch("(?i)test.*") + | + name.regexpMatch("(?i)_*secret") and + type = "secret" + or + name.regexpMatch("(?i)_*(secret|access|private|rsa|aes)_*key") and + type = "key" + or + name.regexpMatch("(?i)_*(encrypted|old|new)?_*pass(wd|word|code|phrase)_*(chars|value)?") and + type = "password" + ) +} + +from DataFlow::Node source, string message, DataFlow::Node sink, string type +where + exists(string val | val = source.getStringValue() and val != "" | + isSensitive(sink, type) and + DataFlow::localFlow(source, sink) and + // whitelist obvious dummy/test values + not val.regexpMatch("(?i)test|password|secret|--- redacted ---") and + not sink.asExpr().(Ident).getName().regexpMatch("(?i)test.*") + ) and + message = "Hard-coded $@." + or + source + .getStringValue() + .regexpMatch("(?s)-+BEGIN\\b.*\\bPRIVATE KEY-+.+-+END\\b.*\\bPRIVATE KEY-+\n?") and + (source.asExpr() instanceof StringLit or source.asExpr() instanceof AddExpr) and + sink = source and + type = "" and + message = "Hard-coded private key." +select sink, message, source, type diff --git a/ql/src/codeql-suites/go-lgtm-full.qls b/ql/src/codeql-suites/go-lgtm-full.qls new file mode 100644 index 00000000..ee466f62 --- /dev/null +++ b/ql/src/codeql-suites/go-lgtm-full.qls @@ -0,0 +1,4 @@ +- description: Standard LGTM queries for Go, including ones not displayed by default +- qlpack: codeql-go +- apply: lgtm-selectors.yml + from: codeql-suite-helpers diff --git a/ql/src/codeql-suites/go-lgtm.qls b/ql/src/codeql-suites/go-lgtm.qls new file mode 100644 index 00000000..5a558df1 --- /dev/null +++ b/ql/src/codeql-suites/go-lgtm.qls @@ -0,0 +1,4 @@ +- description: Standard LGTM queries for Go +- apply: codeql-suites/go-lgtm-full.qls +- apply: lgtm-displayed-only.yml + from: codeql-suite-helpers diff --git a/ql/src/definitions.ql b/ql/src/definitions.ql new file mode 100644 index 00000000..46e28d9f --- /dev/null +++ b/ql/src/definitions.ql @@ -0,0 +1,15 @@ +/** + * @name Jump-to-definition links + * @description Generates use-definition pairs that provide the data + * for jump-to-definition in the code viewer. + * @kind definitions + * @id go/jump-to-definition + */ + +import go + +from Ident def, Ident use, Entity e +where + use.uses(e) and + def.declares(e) +select use, def, "V" diff --git a/ql/src/filters/ClassifyFiles.ql b/ql/src/filters/ClassifyFiles.ql new file mode 100644 index 00000000..f0d13c0a --- /dev/null +++ b/ql/src/filters/ClassifyFiles.ql @@ -0,0 +1,45 @@ +/** + * @name Classify files + * @description This query produces a list of all files in a snapshot that are classified as + * generated code, test code or vendored-in library code. + * @kind file-classifier + * @id go/file-classifier + */ + +import go + +string generatorCommentRegex() { + result = "Generated By\\b.*\\bDo not edit" or + result = "This (file|class|interface|art[ei]fact) (was|is|(has been)) (?:auto[ -]?)?gener(e?)ated" or + result = "Any modifications to this file will be lost" or + result = "This (file|class|interface|art[ei]fact) (was|is) (?:mechanically|automatically) generated" or + result = "The following code was (?:auto[ -]?)?generated (?:by|from)" or + result = "Autogenerated by Thrift" or + result = "(Code g|G)enerated from .* by ANTLR" or + // regular expression recommended for Go code generators (https://golang.org/pkg/cmd/go/internal/generate/) + result = "(^|\n)// Code generated .* DO NOT EDIT\\.($|\n)" +} + +predicate classify(File f, string category) { + // `go test`-style test + f.getBaseName().regexpMatch(".*_test.go") and + exists(FuncDecl fn | + fn.getName().regexpMatch("(Test|Benchmark|Example)[^a-z].*") and + fn.getFile() = f + ) and + category = "test" + or + // vendored code + f.getRelativePath().regexpMatch(".*/vendor/.*") and + category = "library" + or + // generated code + exists(Comment c | c.getFile() = f | + c.getText().regexpMatch("(?i).*\\b(" + concat(generatorCommentRegex(), "|") + ")\\b.*") + ) and + category = "generated" +} + +from File f, string category +where classify(f, category) +select f, category diff --git a/ql/src/go.dbscheme b/ql/src/go.dbscheme new file mode 100644 index 00000000..ffced433 --- /dev/null +++ b/ql/src/go.dbscheme @@ -0,0 +1,402 @@ +/** Auto-generated dbscheme; do not edit. */ + + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +locations_default(unique int id: @location_default, int file: @file ref, int beginLine: int ref, int beginColumn: int ref, + int endLine: int ref, int endColumn: int ref); + +numlines(int element_id: @sourceline ref, int num_lines: int ref, int num_code: int ref, int num_comment: int ref); + +files(unique int id: @file, string name: string ref, string simple: string ref, string ext: string ref, int fromSource: int ref); + +folders(unique int id: @folder, string name: string ref, string simple: string ref); + +containerparent(int parent: @container ref, unique int child: @container ref); + +has_location(unique int locatable: @locatable ref, int location: @location ref); + +comment_groups(unique int id: @comment_group); + +comments(unique int id: @comment, int kind: int ref, int parent: @comment_group ref, int idx: int ref, string text: string ref); + +doc_comments(unique int node: @documentable ref, int comment: @comment_group ref); + +#keyset[parent, idx] +exprs(unique int id: @expr, int kind: int ref, int parent: @exprparent ref, int idx: int ref); + +literals(unique int expr: @expr ref, string value: string ref, string raw: string ref); + +constvalues(unique int expr: @expr ref, string value: string ref, string exact: string ref); + +fields(unique int id: @field, int parent: @fieldparent ref, int idx: int ref); + +#keyset[parent, idx] +stmts(unique int id: @stmt, int kind: int ref, int parent: @stmtparent ref, int idx: int ref); + +#keyset[parent, idx] +decls(unique int id: @decl, int kind: int ref, int parent: @declparent ref, int idx: int ref); + +#keyset[parent, idx] +specs(unique int id: @spec, int kind: int ref, int parent: @gendecl ref, int idx: int ref); + +scopes(unique int id: @scope, int kind: int ref); + +scopenesting(unique int inner: @scope ref, int outer: @scope ref); + +scopenodes(unique int node: @scopenode ref, int scope: @localscope ref); + +objects(unique int id: @object, int kind: int ref, string name: string ref); + +objectscopes(unique int object: @object ref, int scope: @scope ref); + +objecttypes(unique int object: @object ref, int tp: @type ref); + +methodreceivers(unique int method: @object ref, int receiver: @object ref); + +fieldstructs(unique int field: @object ref, int struct: @structtype ref); + +defs(int ident: @ident ref, int object: @object ref); + +uses(int ident: @ident ref, int object: @object ref); + +types(unique int id: @type, int kind: int ref); + +type_of(unique int expr: @expr ref, int tp: @type ref); + +typename(unique int tp: @type ref, string name: string ref); + +key_type(unique int map: @maptype ref, int tp: @type ref); + +element_type(unique int container: @containertype ref, int tp: @type ref); + +base_type(unique int ptr: @pointertype ref, int tp: @type ref); + +underlying_type(unique int named: @namedtype ref, int tp: @type ref); + +#keyset[parent, index] +component_types(int parent: @compositetype ref, int index: int ref, string name: string ref, int tp: @type ref); + +array_length(unique int tp: @arraytype ref, string len: string ref); + +type_objects(unique int tp: @type ref, int object: @object ref); + +packages(unique int id: @package, string name: string ref, string path: string ref, int scope: @packagescope ref); + +@container = @file | @folder; + +@locatable = @node | @localscope; + +@node = @documentable | @exprparent | @fieldparent | @stmtparent | @declparent | @scopenode | @comment_group | @comment; + +@documentable = @file | @field | @spec | @gendecl | @funcdecl; + +@exprparent = @funcdef | @file | @expr | @field | @stmt | @decl | @spec; + +@fieldparent = @decl | @structtypeexpr | @functypeexpr | @interfacetypeexpr; + +@stmtparent = @funcdef | @stmt | @decl; + +@declparent = @file | @declstmt; + +@funcdef = @funclit | @funcdecl; + +@scopenode = @file | @functypeexpr | @blockstmt | @ifstmt | @caseclause | @switchstmt | @commclause | @loopstmt; + +@location = @location_default; + +@sourceline = @locatable; + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment; + +case @expr.kind of + 0 = @badexpr +| 1 = @ident +| 2 = @ellipsis +| 3 = @intlit +| 4 = @floatlit +| 5 = @imaglit +| 6 = @charlit +| 7 = @stringlit +| 8 = @funclit +| 9 = @compositelit +| 10 = @parenexpr +| 11 = @selectorexpr +| 12 = @indexexpr +| 13 = @sliceexpr +| 14 = @typeassertexpr +| 15 = @callorconversionexpr +| 16 = @starexpr +| 17 = @keyvalueexpr +| 18 = @arraytypeexpr +| 19 = @structtypeexpr +| 20 = @functypeexpr +| 21 = @interfacetypeexpr +| 22 = @maptypeexpr +| 23 = @plusexpr +| 24 = @minusexpr +| 25 = @notexpr +| 26 = @complementexpr +| 27 = @derefexpr +| 28 = @addressexpr +| 29 = @arrowexpr +| 30 = @lorexpr +| 31 = @landexpr +| 32 = @eqlexpr +| 33 = @neqexpr +| 34 = @lssexpr +| 35 = @leqexpr +| 36 = @gtrexpr +| 37 = @geqexpr +| 38 = @addexpr +| 39 = @subexpr +| 40 = @orexpr +| 41 = @xorexpr +| 42 = @mulexpr +| 43 = @quoexpr +| 44 = @remexpr +| 45 = @shlexpr +| 46 = @shrexpr +| 47 = @andexpr +| 48 = @andnotexpr +| 49 = @sendchantypeexpr +| 50 = @recvchantypeexpr +| 51 = @sendrcvchantypeexpr; + +@basiclit = @intlit | @floatlit | @imaglit | @charlit | @stringlit; + +@operatorexpr = @logicalexpr | @arithmeticexpr | @bitwiseexpr | @unaryexpr | @binaryexpr; + +@logicalexpr = @logicalunaryexpr | @logicalbinaryexpr; + +@arithmeticexpr = @arithmeticunaryexpr | @arithmeticbinaryexpr; + +@bitwiseexpr = @bitwiseunaryexpr | @bitwisebinaryexpr; + +@unaryexpr = @logicalunaryexpr | @bitwiseunaryexpr | @arithmeticunaryexpr | @derefexpr | @addressexpr | @arrowexpr; + +@logicalunaryexpr = @notexpr; + +@bitwiseunaryexpr = @complementexpr; + +@arithmeticunaryexpr = @plusexpr | @minusexpr; + +@binaryexpr = @logicalbinaryexpr | @bitwisebinaryexpr | @arithmeticbinaryexpr | @comparison; + +@logicalbinaryexpr = @lorexpr | @landexpr; + +@bitwisebinaryexpr = @shiftexpr | @orexpr | @xorexpr | @andexpr | @andnotexpr; + +@arithmeticbinaryexpr = @addexpr | @subexpr | @mulexpr | @quoexpr | @remexpr; + +@shiftexpr = @shlexpr | @shrexpr; + +@comparison = @equalitytest | @relationalcomparison; + +@equalitytest = @eqlexpr | @neqexpr; + +@relationalcomparison = @lssexpr | @leqexpr | @gtrexpr | @geqexpr; + +@chantypeexpr = @sendchantypeexpr | @recvchantypeexpr | @sendrcvchantypeexpr; + +case @stmt.kind of + 0 = @badstmt +| 1 = @declstmt +| 2 = @emptystmt +| 3 = @labeledstmt +| 4 = @exprstmt +| 5 = @sendstmt +| 6 = @incstmt +| 7 = @decstmt +| 8 = @gostmt +| 9 = @deferstmt +| 10 = @returnstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @gotostmt +| 14 = @fallthroughstmt +| 15 = @blockstmt +| 16 = @ifstmt +| 17 = @caseclause +| 18 = @exprswitchstmt +| 19 = @typeswitchstmt +| 20 = @commclause +| 21 = @selectstmt +| 22 = @forstmt +| 23 = @rangestmt +| 24 = @assignstmt +| 25 = @definestmt +| 26 = @addassignstmt +| 27 = @subassignstmt +| 28 = @mulassignstmt +| 29 = @quoassignstmt +| 30 = @remassignstmt +| 31 = @andassignstmt +| 32 = @orassignstmt +| 33 = @xorassignstmt +| 34 = @shlassignstmt +| 35 = @shrassignstmt +| 36 = @andnotassignstmt; + +@incdecstmt = @incstmt | @decstmt; + +@assignment = @simpleassignstmt | @compoundassignstmt; + +@simpleassignstmt = @assignstmt | @definestmt; + +@compoundassignstmt = @addassignstmt | @subassignstmt | @mulassignstmt | @quoassignstmt | @remassignstmt + | @andassignstmt | @orassignstmt | @xorassignstmt | @shlassignstmt | @shrassignstmt | @andnotassignstmt; + +@branchstmt = @breakstmt | @continuestmt | @gotostmt | @fallthroughstmt; + +@switchstmt = @exprswitchstmt | @typeswitchstmt; + +@loopstmt = @forstmt | @rangestmt; + +case @decl.kind of + 0 = @baddecl +| 1 = @importdecl +| 2 = @constdecl +| 3 = @typedecl +| 4 = @vardecl +| 5 = @funcdecl; + +@gendecl = @importdecl | @constdecl | @typedecl | @vardecl; + +case @spec.kind of + 0 = @importspec +| 1 = @valuespec +| 2 = @typespec; + +case @object.kind of + 0 = @pkgobject +| 1 = @decltypeobject +| 2 = @builtintypeobject +| 3 = @declconstobject +| 4 = @builtinconstobject +| 5 = @declvarobject +| 6 = @declfunctionobject +| 7 = @builtinfunctionobject +| 8 = @labelobject; + +@declobject = @decltypeobject | @declconstobject | @declvarobject | @declfunctionobject; + +@builtinobject = @builtintypeobject | @builtinconstobject | @builtinfunctionobject; + +@typeobject = @decltypeobject | @builtintypeobject; + +@valueobject = @constobject | @varobject | @functionobject; + +@constobject = @declconstobject | @builtinconstobject; + +@varobject = @declvarobject; + +@functionobject = @declfunctionobject | @builtinfunctionobject; + +case @scope.kind of + 0 = @universescope +| 1 = @packagescope +| 2 = @localscope; + +case @type.kind of + 0 = @invalidtype +| 1 = @boolexprtype +| 2 = @inttype +| 3 = @int8type +| 4 = @int16type +| 5 = @int32type +| 6 = @int64type +| 7 = @uinttype +| 8 = @uint8type +| 9 = @uint16type +| 10 = @uint32type +| 11 = @uint64type +| 12 = @uintptrtype +| 13 = @float32type +| 14 = @float64type +| 15 = @complex64type +| 16 = @complex128type +| 17 = @stringexprtype +| 18 = @unsafepointertype +| 19 = @boolliteraltype +| 20 = @intliteraltype +| 21 = @runeliteraltype +| 22 = @floatliteraltype +| 23 = @complexliteraltype +| 24 = @stringliteraltype +| 25 = @nilliteraltype +| 26 = @arraytype +| 27 = @slicetype +| 28 = @structtype +| 29 = @pointertype +| 30 = @interfacetype +| 31 = @tupletype +| 32 = @signaturetype +| 33 = @maptype +| 34 = @sendchantype +| 35 = @recvchantype +| 36 = @sendrcvchantype +| 37 = @namedtype; + +@basictype = @booltype | @numerictype | @stringtype | @literaltype | @invalidtype | @uintptrtype | @unsafepointertype; + +@booltype = @boolexprtype | @boolliteraltype; + +@numerictype = @integertype | @floattype | @complextype; + +@integertype = @signedintegertype | @unsignedintegertype; + +@signedintegertype = @inttype | @int8type | @int16type | @int32type | @int64type | @intliteraltype | @runeliteraltype; + +@unsignedintegertype = @uinttype | @uint8type | @uint16type | @uint32type | @uint64type; + +@floattype = @float32type | @float64type | @floatliteraltype; + +@complextype = @complex64type | @complex128type | @complexliteraltype; + +@stringtype = @stringexprtype | @stringliteraltype; + +@literaltype = @boolliteraltype | @intliteraltype | @runeliteraltype | @floatliteraltype | @complexliteraltype + | @stringliteraltype | @nilliteraltype; + +@compositetype = @containertype | @structtype | @pointertype | @interfacetype | @tupletype | @signaturetype | @namedtype; + +@containertype = @arraytype | @slicetype | @maptype | @chantype; + +@chantype = @sendchantype | @recvchantype | @sendrcvchantype; + diff --git a/ql/src/go.dbscheme.stats b/ql/src/go.dbscheme.stats new file mode 100644 index 00000000..2f0cd6fd --- /dev/null +++ b/ql/src/go.dbscheme.stats @@ -0,0 +1,11138 @@ + + + +@duplication +324 + + +@similarity +26 + + +@externalDataElement +6 + + +@location_default +466245 + + +@file +424 + + +@folder +182 + + +@comment_group +10476 + + +@slashslashcomment +22285 + + +@slashstarcomment +621 + + +@ident +204602 + + +@ellipsis +104 + + +@intlit +6819 + + +@floatlit +14 + + +@charlit +926 + + +@stringlit +22476 + + +@funclit +467 + + +@compositelit +1956 + + +@parenexpr +309 + + +@selectorexpr +43698 + + +@indexexpr +3990 + + +@sliceexpr +761 + + +@typeassertexpr +1917 + + +@callorconversionexpr +26652 + + +@starexpr +8793 + + +@keyvalueexpr +14354 + + +@arraytypeexpr +2604 + + +@structtypeexpr +967 + + +@functypeexpr +4902 + + +@interfacetypeexpr +373 + + +@maptypeexpr +971 + + +@plusexpr +8 + + +@minusexpr +247 + + +@notexpr +977 + + +@complementexpr +21 + + +@addressexpr +1406 + + +@arrowexpr +65 + + +@lorexpr +532 + + +@landexpr +1048 + + +@eqlexpr +2779 + + +@neqexpr +3291 + + +@lssexpr +695 + + +@leqexpr +252 + + +@gtrexpr +515 + + +@geqexpr +226 + + +@addexpr +1066 + + +@subexpr +479 + + +@orexpr +127 + + +@xorexpr +13 + + +@mulexpr +187 + + +@quoexpr +39 + + +@remexpr +23 + + +@shlexpr +149 + + +@shrexpr +54 + + +@andexpr +211 + + +@andnotexpr +19 + + +@sendchantypeexpr +7 + + +@recvchantypeexpr +8 + + +@sendrcvchantypeexpr +89 + + +@badexpr +0 + + +@imaglit +0 + + +@derefexpr +0 + + +@field +15838 + + +@declstmt +1252 + + +@labeledstmt +42 + + +@exprstmt +6585 + + +@sendstmt +61 + + +@incstmt +569 + + +@decstmt +71 + + +@gostmt +60 + + +@deferstmt +235 + + +@returnstmt +7452 + + +@breakstmt +250 + + +@continuestmt +509 + + +@gotostmt +8 + + +@fallthroughstmt +8 + + +@blockstmt +16124 + + +@ifstmt +8064 + + +@caseclause +3153 + + +@exprswitchstmt +341 + + +@typeswitchstmt +337 + + +@commclause +51 + + +@selectstmt +26 + + +@forstmt +619 + + +@rangestmt +1749 + + +@assignstmt +6542 + + +@definestmt +7820 + + +@addassignstmt +196 + + +@subassignstmt +15 + + +@mulassignstmt +6 + + +@orassignstmt +55 + + +@xorassignstmt +3 + + +@shlassignstmt +2 + + +@shrassignstmt +3 + + +@andnotassignstmt +3 + + +@badstmt +0 + + +@emptystmt +0 + + +@quoassignstmt +0 + + +@remassignstmt +0 + + +@andassignstmt +0 + + +@importdecl +395 + + +@constdecl +221 + + +@typedecl +1034 + + +@vardecl +1538 + + +@funcdecl +4055 + + +@baddecl +0 + + +@importspec +2786 + + +@valuespec +2601 + + +@typespec +1050 + + +@universescope +1 + + +@packagescope +307 + + +@localscope +30540 + + +@pkgobject +2786 + + +@decltypeobject +3140 + + +@builtintypeobject +20 + + +@declconstobject +8139 + + +@builtinconstobject +4 + + +@declvarobject +43442 + + +@declfunctionobject +16034 + + +@builtinfunctionobject +18 + + +@labelobject +42 + + +@invalidtype +1 + + +@boolexprtype +1 + + +@inttype +1 + + +@int8type +1 + + +@int16type +1 + + +@int32type +1 + + +@int64type +1 + + +@uinttype +1 + + +@uint8type +1 + + +@uint16type +1 + + +@uint32type +1 + + +@uint64type +1 + + +@uintptrtype +1 + + +@float32type +1 + + +@float64type +1 + + +@complex64type +1 + + +@complex128type +1 + + +@stringexprtype +1 + + +@unsafepointertype +1 + + +@boolliteraltype +1 + + +@intliteraltype +1 + + +@runeliteraltype +1 + + +@floatliteraltype +1 + + +@stringliteraltype +1 + + +@nilliteraltype +1 + + +@arraytype +281 + + +@slicetype +529 + + +@structtype +2126 + + +@pointertype +1717 + + +@interfacetype +212 + + +@tupletype +464 + + +@signaturetype +7026 + + +@maptype +372 + + +@sendchantype +13 + + +@recvchantype +10 + + +@sendrcvchantype +31 + + +@namedtype +3125 + + +@complexliteraltype +0 + + +@package +307 + + + + +duplicateCode +324 + + +id +324 + + +relativePath +33 + + +equivClass +95 + + + + +id +relativePath + + +12 + + +1 +2 +324 + + + + + + +id +equivClass + + +12 + + +1 +2 +324 + + + + + + +relativePath +id + + +12 + + +1 +2 +9 + + +2 +3 +11 + + +4 +5 +2 + + +6 +7 +2 + + +7 +10 +3 + + +19 +27 +3 + + +55 +73 +3 + + + + + + +relativePath +equivClass + + +12 + + +1 +2 +17 + + +2 +3 +7 + + +3 +5 +3 + + +6 +7 +3 + + +12 +25 +3 + + + + + + +equivClass +id + + +12 + + +2 +3 +50 + + +3 +4 +15 + + +4 +5 +10 + + +5 +6 +7 + + +6 +9 +8 + + +9 +11 +5 + + + + + + +equivClass +relativePath + + +12 + + +1 +2 +76 + + +2 +3 +17 + + +3 +4 +2 + + + + + + + + +similarCode +26 + + +id +26 + + +relativePath +6 + + +equivClass +10 + + + + +id +relativePath + + +12 + + +1 +2 +26 + + + + + + +id +equivClass + + +12 + + +1 +2 +26 + + + + + + +relativePath +id + + +12 + + +1 +2 +2 + + +2 +3 +1 + + +4 +5 +2 + + +14 +15 +1 + + + + + + +relativePath +equivClass + + +12 + + +1 +2 +3 + + +4 +5 +3 + + + + + + +equivClass +id + + +12 + + +2 +3 +6 + + +3 +4 +2 + + +4 +5 +2 + + + + + + +equivClass +relativePath + + +12 + + +1 +2 +5 + + +2 +3 +5 + + + + + + + + +tokens +42367 + + +id +350 + + +offset +436 + + +beginLine +2483 + + +beginColumn +118 + + +endLine +2483 + + +endColumn +124 + + + + +id +offset + + +12 + + +100 +101 +58 + + +101 +102 +34 + + +102 +103 +12 + + +103 +104 +23 + + +104 +105 +27 + + +105 +106 +23 + + +106 +108 +32 + + +108 +116 +32 + + +116 +121 +22 + + +121 +129 +29 + + +129 +156 +24 + + +162 +377 +28 + + +386 +437 +6 + + + + + + +id +beginLine + + +12 + + +11 +17 +30 + + +18 +21 +24 + + +21 +22 +15 + + +22 +24 +30 + + +24 +27 +30 + + +27 +30 +29 + + +30 +31 +29 + + +31 +33 +28 + + +34 +35 +18 + + +35 +47 +23 + + +47 +50 +28 + + +50 +53 +32 + + +53 +90 +28 + + +92 +122 +6 + + + + + + +id +beginColumn + + +12 + + +6 +9 +22 + + +9 +10 +26 + + +10 +11 +18 + + +11 +14 +23 + + +15 +17 +27 + + +17 +18 +33 + + +18 +20 +27 + + +22 +34 +25 + + +34 +38 +25 + + +38 +40 +21 + + +40 +42 +27 + + +42 +45 +30 + + +45 +52 +27 + + +53 +69 +19 + + + + + + +id +endLine + + +12 + + +11 +17 +30 + + +18 +21 +24 + + +21 +22 +15 + + +22 +24 +30 + + +24 +27 +30 + + +27 +30 +29 + + +30 +31 +29 + + +31 +33 +28 + + +34 +35 +18 + + +35 +47 +23 + + +47 +50 +28 + + +50 +53 +32 + + +53 +90 +28 + + +92 +122 +6 + + + + + + +id +endColumn + + +12 + + +13 +19 +25 + + +19 +21 +26 + + +21 +24 +32 + + +24 +26 +31 + + +26 +28 +32 + + +28 +32 +28 + + +32 +38 +28 + + +38 +43 +26 + + +43 +47 +30 + + +47 +50 +26 + + +50 +53 +32 + + +53 +69 +30 + + +69 +72 +4 + + + + + + +offset +id + + +12 + + +4 +5 +50 + + +6 +7 +10 + + +8 +9 +153 + + +10 +11 +11 + + +12 +13 +36 + + +14 +45 +39 + + +48 +197 +33 + + +223 +293 +4 + + +350 +351 +100 + + + + + + +offset +beginLine + + +12 + + +2 +3 +50 + + +4 +5 +128 + + +6 +7 +66 + + +8 +27 +33 + + +28 +63 +34 + + +63 +253 +33 + + +253 +265 +33 + + +265 +273 +33 + + +273 +293 +26 + + + + + + +offset +beginColumn + + +12 + + +1 +2 +50 + + +2 +3 +12 + + +3 +4 +154 + + +4 +5 +14 + + +5 +9 +33 + + +9 +18 +35 + + +18 +46 +36 + + +46 +54 +40 + + +54 +58 +38 + + +58 +67 +24 + + + + + + +offset +endLine + + +12 + + +2 +3 +50 + + +4 +5 +128 + + +6 +7 +66 + + +8 +27 +33 + + +28 +63 +34 + + +63 +253 +33 + + +253 +265 +33 + + +265 +273 +33 + + +273 +293 +26 + + + + + + +offset +endColumn + + +12 + + +1 +2 +50 + + +2 +3 +6 + + +3 +4 +157 + + +4 +5 +13 + + +5 +6 +34 + + +6 +18 +34 + + +18 +45 +33 + + +47 +59 +33 + + +59 +62 +35 + + +62 +67 +34 + + +67 +71 +7 + + + + + + +beginLine +id + + +12 + + +1 +2 +879 + + +2 +3 +353 + + +3 +4 +266 + + +4 +5 +189 + + +5 +7 +187 + + +7 +11 +179 + + +11 +15 +222 + + +15 +24 +189 + + +24 +35 +19 + + + + + + +beginLine +offset + + +12 + + +1 +2 +29 + + +2 +3 +721 + + +3 +4 +78 + + +4 +5 +253 + + +5 +6 +99 + + +6 +7 +198 + + +7 +9 +171 + + +9 +14 +213 + + +14 +23 +204 + + +23 +33 +196 + + +33 +48 +188 + + +48 +97 +133 + + + + + + +beginLine +beginColumn + + +12 + + +1 +2 +46 + + +2 +3 +1298 + + +3 +4 +146 + + +4 +5 +125 + + +5 +7 +212 + + +7 +10 +211 + + +10 +14 +188 + + +14 +23 +193 + + +23 +41 +64 + + + + + + +beginLine +endLine + + +12 + + +1 +2 +2483 + + + + + + +beginLine +endColumn + + +12 + + +1 +2 +46 + + +2 +3 +1298 + + +3 +4 +142 + + +4 +5 +126 + + +5 +7 +201 + + +7 +10 +206 + + +10 +15 +222 + + +15 +27 +192 + + +27 +41 +50 + + + + + + +beginColumn +id + + +12 + + +1 +2 +6 + + +2 +3 +9 + + +3 +5 +7 + + +5 +9 +10 + + +9 +15 +9 + + +15 +29 +9 + + +30 +58 +10 + + +69 +85 +9 + + +88 +102 +9 + + +103 +120 +9 + + +131 +157 +9 + + +160 +218 +9 + + +221 +245 +9 + + +251 +315 +4 + + + + + + +beginColumn +offset + + +12 + + +1 +2 +5 + + +2 +3 +11 + + +3 +5 +9 + + +5 +10 +9 + + +10 +20 +9 + + +20 +33 +9 + + +36 +54 +9 + + +56 +84 +9 + + +84 +99 +9 + + +99 +120 +10 + + +120 +141 +9 + + +141 +152 +9 + + +154 +200 +9 + + +207 +225 +2 + + + + + + +beginColumn +beginLine + + +12 + + +1 +2 +9 + + +2 +3 +10 + + +3 +4 +8 + + +4 +7 +9 + + +7 +10 +9 + + +10 +20 +9 + + +20 +36 +9 + + +37 +67 +9 + + +68 +112 +9 + + +118 +176 +9 + + +179 +274 +9 + + +278 +352 +9 + + +357 +572 +9 + + +1895 +1896 +1 + + + + + + +beginColumn +endLine + + +12 + + +1 +2 +9 + + +2 +3 +10 + + +3 +4 +8 + + +4 +7 +9 + + +7 +10 +9 + + +10 +20 +9 + + +20 +36 +9 + + +37 +67 +9 + + +68 +112 +9 + + +118 +176 +9 + + +179 +274 +9 + + +278 +352 +9 + + +357 +572 +9 + + +1895 +1896 +1 + + + + + + +beginColumn +endColumn + + +12 + + +1 +2 +13 + + +2 +3 +18 + + +3 +4 +6 + + +4 +6 +10 + + +6 +8 +10 + + +8 +9 +8 + + +9 +11 +7 + + +11 +12 +6 + + +12 +14 +10 + + +14 +17 +9 + + +17 +24 +9 + + +25 +33 +9 + + +35 +38 +3 + + + + + + +endLine +id + + +12 + + +1 +2 +879 + + +2 +3 +353 + + +3 +4 +266 + + +4 +5 +189 + + +5 +7 +187 + + +7 +11 +179 + + +11 +15 +222 + + +15 +24 +189 + + +24 +35 +19 + + + + + + +endLine +offset + + +12 + + +1 +2 +29 + + +2 +3 +721 + + +3 +4 +78 + + +4 +5 +253 + + +5 +6 +99 + + +6 +7 +198 + + +7 +9 +171 + + +9 +14 +213 + + +14 +23 +204 + + +23 +33 +196 + + +33 +48 +188 + + +48 +97 +133 + + + + + + +endLine +beginLine + + +12 + + +1 +2 +2483 + + + + + + +endLine +beginColumn + + +12 + + +1 +2 +46 + + +2 +3 +1298 + + +3 +4 +146 + + +4 +5 +125 + + +5 +7 +212 + + +7 +10 +211 + + +10 +14 +188 + + +14 +23 +193 + + +23 +41 +64 + + + + + + +endLine +endColumn + + +12 + + +1 +2 +46 + + +2 +3 +1298 + + +3 +4 +142 + + +4 +5 +126 + + +5 +7 +201 + + +7 +10 +206 + + +10 +15 +222 + + +15 +27 +192 + + +27 +41 +50 + + + + + + +endColumn +id + + +12 + + +1 +2 +7 + + +2 +3 +10 + + +4 +8 +10 + + +8 +13 +9 + + +13 +19 +11 + + +19 +38 +10 + + +45 +85 +10 + + +85 +105 +10 + + +111 +137 +10 + + +144 +198 +10 + + +198 +249 +10 + + +255 +294 +10 + + +295 +316 +7 + + + + + + +endColumn +offset + + +12 + + +1 +2 +8 + + +2 +3 +5 + + +3 +5 +10 + + +5 +11 +11 + + +11 +18 +10 + + +20 +34 +10 + + +36 +72 +10 + + +73 +84 +10 + + +84 +107 +9 + + +108 +128 +10 + + +128 +146 +10 + + +149 +162 +10 + + +163 +185 +10 + + +186 +187 +1 + + + + + + +endColumn +beginLine + + +12 + + +1 +2 +8 + + +2 +3 +9 + + +3 +5 +11 + + +5 +7 +11 + + +7 +12 +10 + + +13 +29 +10 + + +33 +53 +10 + + +53 +93 +10 + + +94 +144 +10 + + +154 +228 +10 + + +259 +348 +10 + + +352 +437 +10 + + +444 +591 +5 + + + + + + +endColumn +beginColumn + + +12 + + +1 +2 +12 + + +2 +3 +14 + + +3 +4 +12 + + +4 +5 +6 + + +5 +6 +9 + + +6 +9 +9 + + +9 +12 +9 + + +12 +15 +11 + + +15 +16 +7 + + +16 +17 +10 + + +17 +18 +8 + + +18 +20 +9 + + +20 +23 +8 + + + + + + +endColumn +endLine + + +12 + + +1 +2 +8 + + +2 +3 +9 + + +3 +5 +11 + + +5 +7 +11 + + +7 +12 +10 + + +13 +29 +10 + + +33 +53 +10 + + +53 +93 +10 + + +94 +144 +10 + + +154 +228 +10 + + +259 +348 +10 + + +352 +437 +10 + + +444 +591 +5 + + + + + + + + +externalData +12 + + +id +6 + + +path +1 + + +column +2 + + +value +12 + + + + +id +path + + +12 + + +1 +2 +6 + + + + + + +id +column + + +12 + + +2 +3 +6 + + + + + + +id +value + + +12 + + +2 +3 +6 + + + + + + +path +id + + +12 + + +6 +7 +1 + + + + + + +path +column + + +12 + + +2 +3 +1 + + + + + + +path +value + + +12 + + +12 +13 +1 + + + + + + +column +id + + +12 + + +6 +7 +2 + + + + + + +column +path + + +12 + + +1 +2 +2 + + + + + + +column +value + + +12 + + +6 +7 +2 + + + + + + +value +id + + +12 + + +1 +2 +12 + + + + + + +value +path + + +12 + + +1 +2 +12 + + + + + + +value +column + + +12 + + +1 +2 +12 + + + + + + + + +snapshotDate +1 + + +snapshotDate +1 + + + + + +sourceLocationPrefix +1 + + +prefix +1 + + + + + +locations_default +466245 + + +id +466245 + + +file +424 + + +beginLine +10266 + + +beginColumn +172 + + +endLine +10326 + + +endColumn +240 + + + + +id +file + + +12 + + +1 +2 +466245 + + + + + + +id +beginLine + + +12 + + +1 +2 +466245 + + + + + + +id +beginColumn + + +12 + + +1 +2 +466245 + + + + + + +id +endLine + + +12 + + +1 +2 +466245 + + + + + + +id +endColumn + + +12 + + +1 +2 +466245 + + + + + + +file +id + + +12 + + +4 +39 +32 + + +39 +154 +33 + + +154 +236 +32 + + +236 +310 +32 + + +314 +400 +32 + + +403 +507 +32 + + +509 +626 +32 + + +645 +824 +32 + + +831 +1015 +32 + + +1027 +1325 +32 + + +1331 +1629 +32 + + +1649 +2669 +32 + + +2676 +5614 +32 + + +6190 +30971 +7 + + + + + + +file +beginLine + + +12 + + +3 +13 +34 + + +14 +36 +33 + + +36 +54 +35 + + +54 +67 +32 + + +67 +86 +33 + + +86 +109 +32 + + +109 +126 +33 + + +126 +162 +35 + + +162 +201 +32 + + +203 +247 +33 + + +255 +362 +32 + + +363 +591 +32 + + +616 +10182 +28 + + + + + + +file +beginColumn + + +12 + + +3 +15 +33 + + +15 +39 +34 + + +39 +48 +32 + + +48 +55 +37 + + +55 +60 +32 + + +60 +64 +35 + + +64 +68 +33 + + +68 +72 +32 + + +72 +77 +35 + + +77 +83 +34 + + +83 +89 +30 + + +89 +100 +33 + + +100 +133 +24 + + + + + + +file +endLine + + +12 + + +3 +14 +33 + + +14 +42 +32 + + +42 +63 +33 + + +63 +78 +32 + + +78 +97 +32 + + +98 +126 +34 + + +126 +150 +32 + + +150 +194 +33 + + +194 +227 +32 + + +229 +289 +32 + + +290 +380 +32 + + +393 +617 +32 + + +642 +1715 +32 + + +2163 +10325 +3 + + + + + + +file +endColumn + + +12 + + +4 +21 +33 + + +22 +50 +32 + + +50 +61 +32 + + +61 +66 +33 + + +66 +72 +35 + + +72 +76 +32 + + +76 +80 +33 + + +80 +84 +32 + + +84 +88 +34 + + +88 +93 +34 + + +93 +99 +38 + + +99 +107 +33 + + +107 +150 +23 + + + + + + +beginLine +id + + +12 + + +1 +3 +16 + + +3 +4 +6818 + + +4 +9 +922 + + +9 +21 +814 + + +21 +88 +770 + + +88 +847 +770 + + +849 +1526 +156 + + + + + + +beginLine +file + + +12 + + +1 +2 +6910 + + +2 +3 +969 + + +3 +5 +798 + + +5 +22 +786 + + +22 +274 +770 + + +274 +425 +33 + + + + + + +beginLine +beginColumn + + +12 + + +1 +2 +14 + + +2 +3 +6848 + + +3 +6 +878 + + +6 +12 +792 + + +12 +31 +782 + + +31 +72 +770 + + +72 +104 +182 + + + + + + +beginLine +endLine + + +12 + + +1 +2 +7784 + + +2 +3 +959 + + +3 +7 +819 + + +7 +293 +704 + + + + + + +beginLine +endColumn + + +12 + + +1 +2 +14 + + +2 +3 +6830 + + +3 +6 +853 + + +6 +13 +844 + + +13 +37 +780 + + +37 +85 +782 + + +85 +115 +163 + + + + + + +beginColumn +id + + +12 + + +1 +2 +9 + + +2 +5 +14 + + +5 +8 +12 + + +10 +23 +13 + + +24 +41 +13 + + +42 +96 +13 + + +101 +193 +13 + + +199 +415 +13 + + +423 +874 +13 + + +889 +1941 +13 + + +2063 +3759 +13 + + +3855 +6678 +13 + + +7162 +16245 +13 + + +16686 +50401 +7 + + + + + + +beginColumn +file + + +12 + + +1 +2 +12 + + +2 +4 +11 + + +4 +7 +12 + + +7 +15 +13 + + +15 +29 +13 + + +30 +51 +14 + + +64 +94 +13 + + +103 +163 +13 + + +171 +249 +13 + + +250 +314 +14 + + +314 +350 +13 + + +351 +369 +13 + + +369 +384 +13 + + +401 +425 +5 + + + + + + +beginColumn +beginLine + + +12 + + +1 +2 +13 + + +2 +5 +15 + + +5 +10 +15 + + +10 +27 +13 + + +27 +53 +13 + + +53 +99 +13 + + +103 +201 +13 + + +208 +371 +13 + + +383 +577 +13 + + +583 +920 +13 + + +933 +1358 +13 + + +1360 +1497 +13 + + +1504 +10121 +12 + + + + + + +beginColumn +endLine + + +12 + + +1 +2 +13 + + +2 +5 +15 + + +5 +9 +12 + + +9 +21 +13 + + +21 +46 +13 + + +47 +84 +13 + + +86 +158 +13 + + +164 +309 +13 + + +327 +535 +14 + + +551 +872 +13 + + +894 +1302 +13 + + +1308 +1490 +13 + + +1497 +5369 +13 + + +10119 +10120 +1 + + + + + + +beginColumn +endColumn + + +12 + + +1 +2 +11 + + +2 +4 +13 + + +4 +7 +13 + + +7 +11 +12 + + +11 +16 +13 + + +16 +24 +15 + + +24 +29 +15 + + +30 +39 +13 + + +39 +48 +13 + + +48 +61 +13 + + +61 +77 +13 + + +77 +94 +15 + + +97 +194 +13 + + + + + + +endLine +id + + +12 + + +2 +3 +55 + + +3 +4 +6684 + + +4 +8 +905 + + +8 +17 +821 + + +17 +59 +780 + + +59 +458 +775 + + +461 +1485 +306 + + + + + + +endLine +file + + +12 + + +1 +2 +6784 + + +2 +3 +939 + + +3 +5 +866 + + +5 +19 +782 + + +19 +175 +779 + + +175 +425 +176 + + + + + + +endLine +beginLine + + +12 + + +1 +2 +7883 + + +2 +3 +934 + + +3 +7 +796 + + +7 +32 +713 + + + + + + +endLine +beginColumn + + +12 + + +1 +2 +1 + + +2 +3 +6757 + + +3 +5 +665 + + +5 +9 +875 + + +9 +21 +782 + + +21 +54 +784 + + +54 +101 +462 + + + + + + +endLine +endColumn + + +12 + + +1 +2 +61 + + +2 +3 +6705 + + +3 +5 +723 + + +5 +10 +847 + + +10 +24 +801 + + +24 +67 +779 + + +67 +115 +410 + + + + + + +endColumn +id + + +12 + + +1 +2 +14 + + +2 +3 +55 + + +3 +8 +18 + + +8 +24 +18 + + +25 +58 +18 + + +63 +181 +18 + + +183 +560 +18 + + +572 +1841 +18 + + +1841 +4176 +18 + + +4338 +7217 +18 + + +7412 +11140 +18 + + +11533 +17102 +9 + + + + + + +endColumn +file + + +12 + + +1 +2 +72 + + +2 +6 +20 + + +6 +17 +19 + + +17 +41 +18 + + +42 +99 +18 + + +103 +226 +18 + + +228 +321 +18 + + +321 +356 +19 + + +357 +376 +18 + + +376 +409 +18 + + +412 +425 +2 + + + + + + +endColumn +beginLine + + +12 + + +1 +2 +74 + + +2 +7 +21 + + +7 +21 +18 + + +21 +58 +19 + + +61 +154 +18 + + +177 +424 +18 + + +460 +739 +18 + + +778 +1211 +18 + + +1244 +1693 +18 + + +1695 +5518 +18 + + + + + + +endColumn +beginColumn + + +12 + + +1 +2 +17 + + +2 +3 +54 + + +3 +6 +19 + + +6 +14 +20 + + +14 +21 +18 + + +21 +30 +18 + + +30 +38 +20 + + +38 +50 +18 + + +50 +62 +18 + + +62 +71 +20 + + +71 +146 +18 + + + + + + +endColumn +endLine + + +12 + + +1 +2 +74 + + +2 +6 +18 + + +6 +18 +18 + + +18 +52 +18 + + +52 +135 +18 + + +141 +358 +18 + + +383 +675 +18 + + +694 +1066 +18 + + +1156 +1552 +18 + + +1612 +2203 +18 + + +2239 +5502 +4 + + + + + + + + +numlines +424 + + +element_id +424 + + +num_lines +286 + + +num_code +304 + + +num_comment +140 + + + + +element_id +num_lines + + +12 + + +1 +2 +424 + + + + + + +element_id +num_code + + +12 + + +1 +2 +424 + + + + + + +element_id +num_comment + + +12 + + +1 +2 +424 + + + + + + +num_lines +element_id + + +12 + + +1 +2 +191 + + +2 +3 +65 + + +3 +4 +19 + + +4 +6 +11 + + + + + + +num_lines +num_code + + +12 + + +1 +2 +196 + + +2 +3 +64 + + +3 +5 +25 + + +5 +6 +1 + + + + + + +num_lines +num_comment + + +12 + + +1 +2 +197 + + +2 +3 +64 + + +3 +5 +24 + + +5 +6 +1 + + + + + + +num_code +element_id + + +12 + + +1 +2 +230 + + +2 +3 +54 + + +3 +16 +20 + + + + + + +num_code +num_lines + + +12 + + +1 +2 +231 + + +2 +3 +57 + + +3 +16 +16 + + + + + + +num_code +num_comment + + +12 + + +1 +2 +233 + + +2 +3 +57 + + +3 +16 +14 + + + + + + +num_comment +element_id + + +12 + + +1 +2 +63 + + +2 +3 +24 + + +3 +4 +14 + + +4 +5 +14 + + +5 +7 +9 + + +7 +13 +12 + + +13 +18 +4 + + + + + + +num_comment +num_lines + + +12 + + +1 +2 +63 + + +2 +3 +24 + + +3 +4 +16 + + +4 +5 +13 + + +5 +7 +9 + + +7 +13 +11 + + +13 +16 +4 + + + + + + +num_comment +num_code + + +12 + + +1 +2 +63 + + +2 +3 +25 + + +3 +4 +14 + + +4 +5 +14 + + +5 +7 +8 + + +7 +12 +12 + + +12 +16 +4 + + + + + + + + +files +424 + + +id +424 + + +name +424 + + +simple +314 + + +ext +1 + + +fromSource +1 + + + + +id +name + + +12 + + +1 +2 +424 + + + + + + +id +simple + + +12 + + +1 +2 +424 + + + + + + +id +ext + + +12 + + +1 +2 +424 + + + + + + +id +fromSource + + +12 + + +1 +2 +424 + + + + + + +name +id + + +12 + + +1 +2 +424 + + + + + + +name +simple + + +12 + + +1 +2 +424 + + + + + + +name +ext + + +12 + + +1 +2 +424 + + + + + + +name +fromSource + + +12 + + +1 +2 +424 + + + + + + +simple +id + + +12 + + +1 +2 +259 + + +2 +3 +42 + + +3 +21 +13 + + + + + + +simple +name + + +12 + + +1 +2 +259 + + +2 +3 +42 + + +3 +21 +13 + + + + + + +simple +ext + + +12 + + +1 +2 +314 + + + + + + +simple +fromSource + + +12 + + +1 +2 +314 + + + + + + +ext +id + + +12 + + +424 +425 +1 + + + + + + +ext +name + + +12 + + +424 +425 +1 + + + + + + +ext +simple + + +12 + + +314 +315 +1 + + + + + + +ext +fromSource + + +12 + + +1 +2 +1 + + + + + + +fromSource +id + + +12 + + +424 +425 +1 + + + + + + +fromSource +name + + +12 + + +424 +425 +1 + + + + + + +fromSource +simple + + +12 + + +314 +315 +1 + + + + + + +fromSource +ext + + +12 + + +1 +2 +1 + + + + + + + + +folders +182 + + +id +182 + + +name +182 + + +simple +156 + + + + +id +name + + +12 + + +1 +2 +182 + + + + + + +id +simple + + +12 + + +1 +2 +182 + + + + + + +name +id + + +12 + + +1 +2 +182 + + + + + + +name +simple + + +12 + + +1 +2 +182 + + + + + + +simple +id + + +12 + + +1 +2 +139 + + +2 +3 +15 + + +5 +9 +2 + + + + + + +simple +name + + +12 + + +1 +2 +139 + + +2 +3 +15 + + +5 +9 +2 + + + + + + + + +containerparent +605 + + +parent +182 + + +child +605 + + + + +parent +child + + +12 + + +1 +2 +102 + + +2 +3 +27 + + +3 +4 +10 + + +4 +6 +16 + + +6 +12 +15 + + +12 +33 +12 + + + + + + +child +parent + + +12 + + +1 +2 +605 + + + + + + + + +has_location +518263 + + +locatable +518263 + + +location +466245 + + + + +locatable +location + + +12 + + +1 +2 +518263 + + + + + + +location +locatable + + +12 + + +1 +2 +414439 + + +2 +3 +51771 + + +3 +60 +35 + + + + + + + + +comment_groups +10476 + + +id +10476 + + + + + +comments +22906 + + +id +22906 + + +kind +2 + + +parent +10476 + + +idx +156 + + +text +18385 + + + + +id +kind + + +12 + + +1 +2 +22906 + + + + + + +id +parent + + +12 + + +1 +2 +22906 + + + + + + +id +idx + + +12 + + +1 +2 +22906 + + + + + + +id +text + + +12 + + +1 +2 +22906 + + + + + + +kind +id + + +12 + + +621 +622 +1 + + +22285 +22286 +1 + + + + + + +kind +parent + + +12 + + +621 +622 +1 + + +9862 +9863 +1 + + + + + + +kind +idx + + +12 + + +3 +4 +1 + + +156 +157 +1 + + + + + + +kind +text + + +12 + + +597 +598 +1 + + +17788 +17789 +1 + + + + + + +parent +id + + +12 + + +1 +2 +6879 + + +2 +3 +1422 + + +3 +4 +1085 + + +4 +10 +816 + + +10 +157 +274 + + + + + + +parent +kind + + +12 + + +1 +2 +10469 + + +2 +3 +7 + + + + + + +parent +idx + + +12 + + +1 +2 +6879 + + +2 +3 +1422 + + +3 +4 +1085 + + +4 +10 +816 + + +10 +157 +274 + + + + + + +parent +text + + +12 + + +1 +2 +6879 + + +2 +3 +1452 + + +3 +4 +1070 + + +4 +9 +800 + + +9 +131 +275 + + + + + + +idx +id + + +12 + + +1 +2 +36 + + +2 +4 +13 + + +4 +5 +17 + + +5 +7 +14 + + +7 +10 +11 + + +10 +14 +14 + + +14 +22 +13 + + +23 +44 +12 + + +45 +131 +12 + + +152 +2176 +12 + + +3597 +10477 +2 + + + + + + +idx +kind + + +12 + + +1 +2 +153 + + +2 +3 +3 + + + + + + +idx +parent + + +12 + + +1 +2 +36 + + +2 +4 +13 + + +4 +5 +17 + + +5 +7 +14 + + +7 +10 +11 + + +10 +14 +14 + + +14 +22 +13 + + +23 +44 +12 + + +45 +131 +12 + + +152 +2176 +12 + + +3597 +10477 +2 + + + + + + +idx +text + + +12 + + +1 +2 +36 + + +2 +4 +14 + + +4 +5 +16 + + +5 +6 +14 + + +6 +9 +13 + + +9 +12 +13 + + +12 +19 +12 + + +19 +35 +12 + + +36 +104 +12 + + +115 +1422 +12 + + +2885 +9167 +2 + + + + + + +text +id + + +12 + + +1 +2 +17373 + + +2 +1879 +1012 + + + + + + +text +kind + + +12 + + +1 +2 +18385 + + + + + + +text +parent + + +12 + + +1 +2 +17397 + + +2 +792 +988 + + + + + + +text +idx + + +12 + + +1 +2 +18239 + + +2 +105 +146 + + + + + + + + +doc_comments +3813 + + +node +3813 + + +comment +3813 + + + + +node +comment + + +12 + + +1 +2 +3813 + + + + + + +comment +node + + +12 + + +1 +2 +3813 + + + + + + + + +exprs +362188 + + +id +362188 + + +kind +49 + + +parent +190395 + + +idx +5159 + + + + +id +kind + + +12 + + +1 +2 +362188 + + + + + + +id +parent + + +12 + + +1 +2 +362188 + + + + + + +id +idx + + +12 + + +1 +2 +362188 + + + + + + +kind +id + + +12 + + +7 +14 +4 + + +14 +24 +4 + + +39 +90 +4 + + +104 +188 +4 + + +211 +253 +4 + + +309 +480 +4 + + +515 +762 +4 + + +926 +978 +4 + + +1048 +1918 +4 + + +1956 +3292 +4 + + +3990 +8794 +4 + + +14354 +43699 +4 + + +204602 +204603 +1 + + + + + + +kind +parent + + +12 + + +7 +14 +4 + + +14 +24 +4 + + +39 +90 +4 + + +104 +163 +4 + + +197 +246 +4 + + +291 +476 +4 + + +505 +717 +4 + + +858 +972 +4 + + +1037 +1359 +4 + + +1738 +2605 +4 + + +3190 +6559 +4 + + +8580 +39533 +4 + + +136841 +136842 +1 + + + + + + +kind +idx + + +12 + + +1 +2 +4 + + +2 +3 +8 + + +3 +4 +12 + + +4 +5 +6 + + +5 +7 +4 + + +7 +9 +4 + + +13 +19 +4 + + +19 +34 +4 + + +36 +5141 +3 + + + + + + +parent +id + + +12 + + +1 +2 +54170 + + +2 +3 +119789 + + +3 +5 +14523 + + +5 +5142 +1913 + + + + + + +parent +kind + + +12 + + +1 +2 +113071 + + +2 +3 +72139 + + +3 +7 +5185 + + + + + + +parent +idx + + +12 + + +1 +2 +54170 + + +2 +3 +119789 + + +3 +5 +14523 + + +5 +5142 +1913 + + + + + + +idx +id + + +12 + + +1 +2 +3912 + + +2 +3 +178 + + +3 +4 +789 + + +4 +157554 +280 + + + + + + +idx +kind + + +12 + + +1 +2 +5098 + + +2 +49 +61 + + + + + + +idx +parent + + +12 + + +1 +2 +3912 + + +2 +3 +178 + + +3 +4 +789 + + +4 +157554 +280 + + + + + + + + +literals +234837 + + +expr +234837 + + +value +23345 + + +raw +24992 + + + + +expr +value + + +12 + + +1 +2 +234837 + + + + + + +expr +raw + + +12 + + +1 +2 +234837 + + + + + + +value +expr + + +12 + + +1 +2 +13916 + + +2 +3 +2831 + + +3 +4 +1474 + + +4 +7 +2105 + + +7 +19 +1801 + + +19 +12102 +1218 + + + + + + +value +raw + + +12 + + +1 +2 +21746 + + +2 +5 +1599 + + + + + + +raw +expr + + +12 + + +1 +2 +15206 + + +2 +3 +3040 + + +3 +4 +1558 + + +4 +7 +2152 + + +7 +20 +1881 + + +20 +12095 +1155 + + + + + + +raw +value + + +12 + + +1 +2 +24992 + + + + + + + + +constvalues +49031 + + +expr +49031 + + +value +16095 + + +exact +16095 + + + + +expr +value + + +12 + + +1 +2 +49031 + + + + + + +expr +exact + + +12 + + +1 +2 +49031 + + + + + + +value +expr + + +12 + + +1 +2 +14315 + + +2 +4 +1288 + + +4 +12118 +492 + + + + + + +value +exact + + +12 + + +1 +2 +16095 + + + + + + +exact +expr + + +12 + + +1 +2 +14315 + + +2 +4 +1288 + + +4 +12118 +492 + + + + + + +exact +value + + +12 + + +1 +2 +16095 + + + + + + + + +fields +15838 + + +id +15838 + + +parent +7836 + + +idx +44 + + + + +id +parent + + +12 + + +1 +2 +15838 + + + + + + +id +idx + + +12 + + +1 +2 +15838 + + + + + + +parent +id + + +12 + + +1 +2 +3974 + + +2 +3 +1849 + + +3 +4 +1129 + + +4 +6 +659 + + +6 +42 +225 + + + + + + +parent +idx + + +12 + + +1 +2 +3974 + + +2 +3 +1849 + + +3 +4 +1129 + + +4 +6 +659 + + +6 +42 +225 + + + + + + +idx +id + + +12 + + +1 +2 +11 + + +2 +3 +4 + + +3 +5 +4 + + +6 +13 +4 + + +13 +22 +4 + + +22 +51 +4 + + +59 +81 +4 + + +106 +441 +4 + + +685 +4584 +4 + + +5760 +5761 +1 + + + + + + +idx +parent + + +12 + + +1 +2 +11 + + +2 +3 +4 + + +3 +5 +4 + + +6 +13 +4 + + +13 +22 +4 + + +22 +51 +4 + + +59 +81 +4 + + +106 +441 +4 + + +685 +4584 +4 + + +5760 +5761 +1 + + + + + + + + +stmts +62211 + + +id +62211 + + +kind +32 + + +parent +34831 + + +idx +79 + + + + +id +kind + + +12 + + +1 +2 +62211 + + + + + + +id +parent + + +12 + + +1 +2 +62211 + + + + + + +id +idx + + +12 + + +1 +2 +62211 + + + + + + +kind +id + + +12 + + +2 +3 +1 + + +3 +4 +3 + + +6 +7 +1 + + +8 +9 +2 + + +15 +27 +2 + + +42 +52 +2 + + +55 +61 +2 + + +61 +72 +2 + + +196 +236 +2 + + +250 +338 +2 + + +341 +510 +2 + + +569 +620 +2 + + +1252 +1750 +2 + + +3153 +6543 +2 + + +6585 +7453 +2 + + +7820 +8065 +2 + + +16124 +16125 +1 + + + + + + +kind +parent + + +12 + + +2 +3 +1 + + +3 +4 +3 + + +6 +7 +1 + + +8 +9 +2 + + +14 +15 +1 + + +25 +26 +2 + + +40 +50 +2 + + +56 +62 +2 + + +71 +179 +2 + + +215 +251 +2 + + +327 +331 +2 + + +509 +540 +2 + + +540 +679 +2 + + +977 +1405 +2 + + +4834 +4929 +2 + + +4994 +5332 +2 + + +7452 +15658 +2 + + + + + + +kind +idx + + +12 + + +2 +3 +5 + + +3 +4 +2 + + +4 +6 +2 + + +7 +8 +4 + + +8 +10 +2 + + +10 +14 +2 + + +14 +15 +4 + + +15 +17 +2 + + +25 +28 +2 + + +33 +34 +2 + + +42 +45 +2 + + +46 +56 +2 + + +79 +80 +1 + + + + + + +parent +id + + +12 + + +1 +2 +24373 + + +2 +3 +5301 + + +3 +5 +3218 + + +5 +80 +1939 + + + + + + +parent +kind + + +12 + + +1 +2 +26161 + + +2 +3 +5027 + + +3 +5 +3043 + + +5 +11 +600 + + + + + + +parent +idx + + +12 + + +1 +2 +24373 + + +2 +3 +5301 + + +3 +5 +3218 + + +5 +80 +1939 + + + + + + +idx +id + + +12 + + +1 +2 +4 + + +2 +3 +20 + + +5 +6 +3 + + +6 +7 +9 + + +7 +12 +7 + + +12 +17 +6 + + +17 +28 +6 + + +31 +86 +6 + + +106 +262 +6 + + +328 +1035 +6 + + +1423 +21030 +6 + + + + + + +idx +kind + + +12 + + +1 +2 +10 + + +2 +3 +22 + + +3 +4 +6 + + +4 +6 +7 + + +6 +7 +3 + + +7 +8 +5 + + +8 +10 +6 + + +10 +13 +7 + + +13 +19 +6 + + +19 +30 +6 + + +32 +33 +1 + + + + + + +idx +parent + + +12 + + +1 +2 +4 + + +2 +3 +20 + + +5 +6 +3 + + +6 +7 +9 + + +7 +12 +7 + + +12 +17 +6 + + +17 +28 +6 + + +31 +86 +6 + + +106 +262 +6 + + +328 +1035 +6 + + +1423 +21030 +6 + + + + + + + + +decls +7243 + + +id +7243 + + +kind +5 + + +parent +1661 + + +idx +222 + + + + +id +kind + + +12 + + +1 +2 +7243 + + + + + + +id +parent + + +12 + + +1 +2 +7243 + + + + + + +id +idx + + +12 + + +1 +2 +7243 + + + + + + +kind +id + + +12 + + +221 +222 +1 + + +395 +396 +1 + + +1034 +1035 +1 + + +1538 +1539 +1 + + +4055 +4056 +1 + + + + + + +kind +parent + + +12 + + +161 +162 +1 + + +275 +276 +1 + + +395 +396 +1 + + +400 +401 +1 + + +1348 +1349 +1 + + + + + + +kind +idx + + +12 + + +1 +2 +1 + + +30 +31 +1 + + +105 +106 +1 + + +163 +164 +1 + + +219 +220 +1 + + + + + + +parent +id + + +12 + + +1 +2 +1257 + + +2 +7 +136 + + +7 +14 +132 + + +14 +52 +125 + + +52 +223 +11 + + + + + + +parent +kind + + +12 + + +1 +2 +1259 + + +2 +3 +90 + + +3 +4 +154 + + +4 +5 +112 + + +5 +6 +46 + + + + + + +parent +idx + + +12 + + +1 +2 +1257 + + +2 +7 +136 + + +7 +14 +132 + + +14 +52 +125 + + +52 +223 +11 + + + + + + +idx +id + + +12 + + +1 +2 +31 + + +2 +3 +17 + + +3 +4 +19 + + +4 +5 +47 + + +5 +6 +2 + + +6 +7 +48 + + +7 +27 +18 + + +27 +57 +17 + + +60 +269 +17 + + +287 +1662 +6 + + + + + + +idx +kind + + +12 + + +1 +2 +59 + + +2 +3 +59 + + +3 +4 +76 + + +4 +5 +27 + + +5 +6 +1 + + + + + + +idx +parent + + +12 + + +1 +2 +31 + + +2 +3 +17 + + +3 +4 +19 + + +4 +5 +47 + + +5 +6 +2 + + +6 +7 +48 + + +7 +27 +18 + + +27 +57 +17 + + +60 +269 +17 + + +287 +1662 +6 + + + + + + + + +specs +6437 + + +id +6437 + + +kind +3 + + +parent +3188 + + +idx +108 + + + + +id +kind + + +12 + + +1 +2 +6437 + + + + + + +id +parent + + +12 + + +1 +2 +6437 + + + + + + +id +idx + + +12 + + +1 +2 +6437 + + + + + + +kind +id + + +12 + + +1050 +1051 +1 + + +2601 +2602 +1 + + +2786 +2787 +1 + + + + + + +kind +parent + + +12 + + +395 +396 +1 + + +1034 +1035 +1 + + +1759 +1760 +1 + + + + + + +kind +idx + + +12 + + +14 +15 +1 + + +29 +30 +1 + + +108 +109 +1 + + + + + + +parent +id + + +12 + + +1 +2 +2663 + + +2 +6 +256 + + +6 +17 +241 + + +17 +109 +28 + + + + + + +parent +kind + + +12 + + +1 +2 +3188 + + + + + + +parent +idx + + +12 + + +1 +2 +2663 + + +2 +6 +256 + + +6 +17 +241 + + +17 +109 +28 + + + + + + +idx +id + + +12 + + +1 +2 +53 + + +2 +3 +26 + + +3 +14 +9 + + +17 +77 +9 + + +99 +448 +9 + + +525 +3189 +2 + + + + + + +idx +kind + + +12 + + +1 +2 +79 + + +2 +3 +15 + + +3 +4 +14 + + + + + + +idx +parent + + +12 + + +1 +2 +53 + + +2 +3 +26 + + +3 +14 +9 + + +17 +77 +9 + + +99 +448 +9 + + +525 +3189 +2 + + + + + + + + +scopes +30848 + + +id +30848 + + +kind +3 + + + + +id +kind + + +12 + + +1 +2 +30848 + + + + + + +kind +id + + +12 + + +1 +2 +1 + + +307 +308 +1 + + +30540 +30541 +1 + + + + + + + + +scopenesting +30847 + + +inner +30847 + + +outer +18221 + + + + +inner +outer + + +12 + + +1 +2 +30847 + + + + + + +outer +inner + + +12 + + +1 +2 +14251 + + +2 +3 +2094 + + +3 +7 +1429 + + +7 +308 +447 + + + + + + + + +scopenodes +30540 + + +node +30540 + + +scope +30540 + + + + +node +scope + + +12 + + +1 +2 +30540 + + + + + + +scope +node + + +12 + + +1 +2 +30540 + + + + + + + + +objects +73625 + + +id +73625 + + +kind +9 + + +name +27495 + + + + +id +kind + + +12 + + +1 +2 +73625 + + + + + + +id +name + + +12 + + +1 +2 +73625 + + + + + + +kind +id + + +12 + + +4 +5 +1 + + +18 +19 +1 + + +20 +21 +1 + + +42 +43 +1 + + +2786 +2787 +1 + + +3140 +3141 +1 + + +8139 +8140 +1 + + +16034 +16035 +1 + + +43442 +43443 +1 + + + + + + +kind +name + + +12 + + +4 +5 +1 + + +18 +19 +1 + + +20 +21 +1 + + +35 +36 +1 + + +175 +176 +1 + + +2625 +2626 +1 + + +7764 +7765 +1 + + +8885 +8886 +1 + + +9855 +9856 +1 + + + + + + +name +id + + +12 + + +1 +2 +22803 + + +2 +3 +2329 + + +3 +21 +2063 + + +21 +1736 +300 + + + + + + +name +kind + + +12 + + +1 +2 +25954 + + +2 +5 +1541 + + + + + + + + +objectscopes +46799 + + +object +46799 + + +scope +11726 + + + + +object +scope + + +12 + + +1 +2 +46799 + + + + + + +scope +object + + +12 + + +1 +2 +6046 + + +2 +3 +2475 + + +3 +4 +1088 + + +4 +6 +1078 + + +6 +21 +884 + + +21 +2383 +155 + + + + + + + + +objecttypes +73623 + + +object +73623 + + +tp +11910 + + + + +object +tp + + +12 + + +1 +2 +73623 + + + + + + +tp +object + + +12 + + +1 +2 +7065 + + +2 +3 +1845 + + +3 +4 +801 + + +4 +7 +991 + + +7 +26 +898 + + +26 +3695 +310 + + + + + + + + +methodreceivers +8846 + + +method +8846 + + +receiver +8846 + + + + +method +receiver + + +12 + + +1 +2 +8846 + + + + + + +receiver +method + + +12 + + +1 +2 +8846 + + + + + + + + +fieldstructs +9638 + + +field +9638 + + +struct +2125 + + + + +field +struct + + +12 + + +1 +2 +9638 + + + + + + +struct +field + + +12 + + +1 +2 +232 + + +2 +3 +609 + + +3 +4 +394 + + +4 +5 +256 + + +5 +6 +180 + + +6 +8 +185 + + +8 +13 +161 + + +13 +61 +108 + + + + + + + + +defs +33063 + + +ident +33063 + + +object +32871 + + + + +ident +object + + +12 + + +1 +2 +33063 + + + + + + +object +ident + + +12 + + +1 +2 +32762 + + +2 +22 +109 + + + + + + + + +uses +170876 + + +ident +170876 + + +object +34242 + + + + +ident +object + + +12 + + +1 +2 +170876 + + + + + + +object +ident + + +12 + + +1 +2 +12640 + + +2 +3 +7967 + + +3 +4 +4246 + + +4 +5 +2534 + + +5 +7 +2632 + + +7 +14 +2725 + + +14 +12095 +1498 + + + + + + + + +types +15931 + + +id +15931 + + +kind +37 + + + + +id +kind + + +12 + + +1 +2 +15931 + + + + + + +kind +id + + +12 + + +1 +2 +25 + + +10 +32 +3 + + +212 +373 +3 + + +464 +1718 +3 + + +2126 +7027 +3 + + + + + + + + +type_of +339195 + + +expr +339195 + + +tp +7136 + + + + +expr +tp + + +12 + + +1 +2 +339195 + + + + + + +tp +expr + + +12 + + +1 +2 +1642 + + +2 +3 +824 + + +3 +4 +558 + + +4 +5 +340 + + +5 +7 +641 + + +7 +10 +619 + + +10 +15 +607 + + +15 +23 +581 + + +23 +43 +537 + + +43 +149 +538 + + +150 +40545 +249 + + + + + + + + +typename +3125 + + +tp +3125 + + +name +2616 + + + + +tp +name + + +12 + + +1 +2 +3125 + + + + + + +name +tp + + +12 + + +1 +2 +2321 + + +2 +3 +202 + + +3 +16 +93 + + + + + + + + +key_type +372 + + +map +372 + + +tp +130 + + + + +map +tp + + +12 + + +1 +2 +372 + + + + + + +tp +map + + +12 + + +1 +2 +91 + + +2 +3 +14 + + +3 +6 +11 + + +6 +9 +11 + + +11 +120 +3 + + + + + + + + +element_type +1236 + + +container +1236 + + +tp +777 + + + + +container +tp + + +12 + + +1 +2 +1236 + + + + + + +tp +container + + +12 + + +1 +2 +654 + + +2 +3 +80 + + +3 +59 +43 + + + + + + + + +base_type +1717 + + +ptr +1717 + + +tp +1717 + + + + +ptr +tp + + +12 + + +1 +2 +1717 + + + + + + +tp +ptr + + +12 + + +1 +2 +1717 + + + + + + + + +underlying_type +3125 + + +named +3125 + + +tp +2425 + + + + +named +tp + + +12 + + +1 +2 +3125 + + + + + + +tp +named + + +12 + + +1 +2 +2271 + + +2 +128 +154 + + + + + + + + +component_types +31625 + + +parent +9824 + + +index +70 + + +name +4909 + + +tp +3727 + + + + +parent +index + + +12 + + +1 +2 +1060 + + +2 +3 +3512 + + +3 +4 +2498 + + +4 +5 +1248 + + +5 +6 +693 + + +6 +17 +742 + + +17 +61 +71 + + + + + + +parent +name + + +12 + + +1 +2 +7812 + + +2 +3 +644 + + +3 +6 +888 + + +6 +60 +480 + + + + + + +parent +tp + + +12 + + +1 +2 +1974 + + +2 +3 +3961 + + +3 +4 +2186 + + +4 +5 +965 + + +5 +31 +737 + + +38 +39 +1 + + + + + + +index +parent + + +12 + + +1 +2 +16 + + +2 +4 +5 + + +4 +10 +5 + + +10 +15 +6 + + +15 +21 +6 + + +22 +37 +6 + + +43 +61 +6 + + +71 +140 +6 + + +173 +446 +6 + + +710 +5235 +6 + + +5625 +8988 +2 + + + + + + +index +name + + +12 + + +1 +2 +22 + + +2 +5 +6 + + +6 +10 +3 + + +10 +16 +6 + + +16 +23 +6 + + +23 +43 +6 + + +45 +79 +6 + + +86 +183 +6 + + +205 +726 +6 + + +911 +1110 +3 + + + + + + +index +tp + + +12 + + +1 +2 +16 + + +2 +4 +5 + + +4 +8 +6 + + +9 +13 +6 + + +13 +17 +6 + + +17 +24 +6 + + +27 +40 +6 + + +41 +73 +6 + + +102 +196 +6 + + +258 +1618 +6 + + +1881 +1882 +1 + + + + + + +name +parent + + +12 + + +1 +2 +3480 + + +2 +3 +757 + + +3 +5 +376 + + +5 +7783 +296 + + + + + + +name +index + + +12 + + +1 +2 +3830 + + +2 +3 +629 + + +3 +6 +379 + + +6 +28 +71 + + + + + + +name +tp + + +12 + + +1 +2 +4080 + + +2 +3 +447 + + +3 +15 +370 + + +15 +2623 +12 + + + + + + +tp +parent + + +12 + + +1 +2 +1794 + + +2 +3 +679 + + +3 +4 +380 + + +4 +6 +335 + + +6 +11 +286 + + +11 +1888 +253 + + + + + + +tp +index + + +12 + + +1 +2 +1850 + + +2 +3 +757 + + +3 +4 +474 + + +4 +5 +314 + + +5 +11 +293 + + +11 +48 +39 + + + + + + +tp +name + + +12 + + +1 +2 +2509 + + +2 +3 +743 + + +3 +5 +303 + + +5 +639 +172 + + + + + + + + +array_length +281 + + +tp +281 + + +len +101 + + + + +tp +len + + +12 + + +1 +2 +281 + + + + + + +len +tp + + +12 + + +1 +2 +62 + + +2 +3 +15 + + +3 +5 +9 + + +5 +11 +8 + + +11 +27 +7 + + + + + + + + +type_objects +3125 + + +tp +3125 + + +object +3125 + + + + +tp +object + + +12 + + +1 +2 +3125 + + + + + + +object +tp + + +12 + + +1 +2 +3125 + + + + + + + + +packages +307 + + +id +307 + + +name +249 + + +path +307 + + +scope +307 + + + + +id +name + + +12 + + +1 +2 +307 + + + + + + +id +path + + +12 + + +1 +2 +307 + + + + + + +id +scope + + +12 + + +1 +2 +307 + + + + + + +name +id + + +12 + + +1 +2 +228 + + +2 +4 +20 + + +37 +38 +1 + + + + + + +name +path + + +12 + + +1 +2 +228 + + +2 +4 +20 + + +37 +38 +1 + + + + + + +name +scope + + +12 + + +1 +2 +228 + + +2 +4 +20 + + +37 +38 +1 + + + + + + +path +id + + +12 + + +1 +2 +307 + + + + + + +path +name + + +12 + + +1 +2 +307 + + + + + + +path +scope + + +12 + + +1 +2 +307 + + + + + + +scope +id + + +12 + + +1 +2 +307 + + + + + + +scope +name + + +12 + + +1 +2 +307 + + + + + + +scope +path + + +12 + + +1 +2 +307 + + + + + + + + + diff --git a/ql/src/go.qll b/ql/src/go.qll new file mode 100644 index 00000000..b6b4f339 --- /dev/null +++ b/ql/src/go.qll @@ -0,0 +1,30 @@ +/** + * Provides classes for working with Go programs. + */ + +import Customizations +import semmle.go.AST +import semmle.go.Comments +import semmle.go.Concepts +import semmle.go.Decls +import semmle.go.Expr +import semmle.go.Files +import semmle.go.Locations +import semmle.go.Packages +import semmle.go.Scopes +import semmle.go.Stmt +import semmle.go.StringConcatenation +import semmle.go.Types +import semmle.go.controlflow.BasicBlocks +import semmle.go.controlflow.ControlFlowGraph +import semmle.go.controlflow.IR +import semmle.go.dataflow.DataFlow +import semmle.go.dataflow.GlobalValueNumbering +import semmle.go.dataflow.TaintTracking +import semmle.go.dataflow.SSA +import semmle.go.frameworks.HTTP +import semmle.go.frameworks.SystemCommandExecutors +import semmle.go.frameworks.SQL +import semmle.go.frameworks.Stdlib +import semmle.go.security.FlowSources +import semmle.go.Util diff --git a/ql/src/qlpack.yml b/ql/src/qlpack.yml new file mode 100644 index 00000000..618d0d38 --- /dev/null +++ b/ql/src/qlpack.yml @@ -0,0 +1,4 @@ +name: codeql-go +version: 0.0.0 +dbscheme: go.dbscheme +suites: codeql-suites diff --git a/ql/src/queries.xml b/ql/src/queries.xml new file mode 100644 index 00000000..6a456b21 --- /dev/null +++ b/ql/src/queries.xml @@ -0,0 +1 @@ + diff --git a/ql/src/semmle/go/AST.qll b/ql/src/semmle/go/AST.qll new file mode 100644 index 00000000..3d1dddf8 --- /dev/null +++ b/ql/src/semmle/go/AST.qll @@ -0,0 +1,134 @@ +/** + * Provides classes for working with AST nodes. + */ + +import go + +/** + * An AST node. + */ +class AstNode extends @node, Locatable { + /** + * Gets the `i`th child node of this node. + * + * Note that the precise indices of child nodes are considered an implementation detail + * and are subject to change without notice. + */ + AstNode getChild(int i) { + result = this.(ExprParent).getChildExpr(i) or + result = this.(StmtParent).getChildStmt(i) or + result = this.(DeclParent).getDecl(i) or + result = this.(GenDecl).getSpec(i) or + fields(result, this, i) + } + + /** + * Gets a child node of this node. + */ + AstNode getAChild() { result = getChild(_) } + + /** + * Gets the number of child nodes of this node. + */ + int getNumChild() { result = count(getAChild()) } + + /** Gets the parent node of this AST node, if any. */ + AstNode getParent() { this = result.getAChild() } + + /** Gets the parent node of this AST node, but without crossing function boundaries. */ + private AstNode parentInSameFunction() { + result = getParent() and + not this instanceof FuncDef + } + + /** Gets the innermost function definition to which this AST node belongs, if any. */ + FuncDef getEnclosingFunction() { result = getParent().parentInSameFunction*() } + + override string toString() { result = "AST node" } +} + +/** + * An AST node whose children include expressions. + */ +class ExprParent extends @exprparent, AstNode { + /** + * Gets the `i`th child expression of this node. + * + * Note that the precise indices of child expressions are considered an implementation detail + * and are subject to change without notice. + */ + Expr getChildExpr(int i) { exprs(result, _, this, i) } + + /** + * Gets an expression that is a child node of this node in the AST. + */ + Expr getAChildExpr() { result = getChildExpr(_) } + + /** + * Gets the number of child expressions of this node. + */ + int getNumChildExpr() { result = count(getAChildExpr()) } +} + +/** + * An AST node whose children include statements. + */ +class StmtParent extends @stmtparent, AstNode { + /** + * Gets the `i`th child statement of this node. + * + * Note that the precise indices of child statements are considered an implementation detail + * and are subject to change without notice. + */ + Stmt getChildStmt(int i) { stmts(result, _, this, i) } + + /** + * Gets a statement that is a child node of this node in the AST. + */ + Stmt getAChildStmt() { result = getChildStmt(_) } + + /** + * Gets the number of child statements of this node. + */ + int getNumChildStmt() { result = count(getAChildStmt()) } +} + +/** + * An AST node whose children include declarations. + */ +class DeclParent extends @declparent, AstNode { + /** + * Gets the `i`th child declaration of this node. + * + * Note that the precise indices of declarations are considered an implementation detail + * and are subject to change without notice. + */ + Decl getDecl(int i) { decls(result, _, this, i) } + + /** + * Gets a child declaration of this node in the AST. + */ + Decl getADecl() { result = getDecl(_) } + + /** + * Gets the number of child declarations of this node. + */ + int getNumDecl() { result = count(getADecl()) } +} + +/** + * An AST node which may induce a scope. + * + * The following nodes may induce scopes: + * + * - files + * - block statements, `if` statements, `switch` statements, `case` clauses, comm clauses, loops + * - function type expressions + * + * Note that functions themselves do not induce a scope, it is their type declaration that induces + * the scope. + */ +class ScopeNode extends @scopenode, AstNode { + /** Gets the scope induced by this node, if any. */ + LocalScope getScope() { scopenodes(this, result) } +} diff --git a/ql/src/semmle/go/Comments.qll b/ql/src/semmle/go/Comments.qll new file mode 100644 index 00000000..25b448cf --- /dev/null +++ b/ql/src/semmle/go/Comments.qll @@ -0,0 +1,110 @@ +/** + * Provides classes for working with code comments. + */ + +import go + +/** + * A code comment. + */ +class Comment extends @comment, AstNode { + /** + * Gets the text of this comment, not including delimiters. + */ + string getText() { comments(this, _, _, _, result) } + + /** + * Gets the comment group to which this comment belongs. + */ + CommentGroup getGroup() { this = result.getAComment() } + + override string toString() { result = "comment" } +} + +/** + * A comment group, that is, a sequence of comments without any intervening tokens or + * empty lines. + */ +class CommentGroup extends @comment_group, AstNode { + /** Gets the `i`th comment in this group (0-based indexing). */ + Comment getComment(int i) { comments(result, _, this, i, _) } + + /** Gets a comment in this group. */ + Comment getAComment() { result = getComment(_) } + + /** Gets the number of comments in this group. */ + int getNumComment() { result = count(getAComment()) } + + override string toString() { result = "comment group" } +} + +/** + * A program element to which a documentation comment group may be attached. + */ +class Documentable extends AstNode, @documentable { + /** Gets the documentation comment group attached to this element, if any. */ + DocComment getDocumentation() { this = result.getDocumentedElement() } +} + +/** + * A comment group that is attached to a program element as documentation. + */ +class DocComment extends CommentGroup { + Documentable node; + + DocComment() { doc_comments(node, this) } + + /** Gets the program element documented by this comment group. */ + Documentable getDocumentedElement() { result = node } +} + +/** + * A single-line comment starting with `//`. + */ +class SlashSlashComment extends @slashslashcomment, Comment { } + +/** + * A block comment starting with `/*` and ending with */. + */ +class SlashStarComment extends @slashstarcomment, Comment { } + +/** + * A single-line comment starting with `//`. + */ +class LineComment = SlashSlashComment; + +/** + * A block comment starting with `/*` and ending with */. + */ +class BlockComment = SlashStarComment; + +/** Holds if `c` starts at `line`, `col` in `f`, and precedes the package declaration. */ +private predicate isInitialComment(Comment c, File f, int line, int col) { + c.hasLocationInfo(f.getAbsolutePath(), line, col, _, _) and + line < f.getPackageNameExpr().getLocation().getStartLine() +} + +/** Gets the `i`th initial comment in `f` (0-based). */ +private Comment getInitialComment(File f, int i) { + result = rank[i + 1](Comment c, int line, int col | + isInitialComment(c, f, line, col) + | + c order by line, col + ) +} + +/** + * A build constraint comment of the form `// +build ...`. + */ +class BuildConstraintComment extends LineComment { + BuildConstraintComment() { + // a line comment preceding the package declaration, itself only preceded by + // line comments + exists(File f, int i | + this = getInitialComment(f, i) and + not getInitialComment(f, [0 .. i - 1]) instanceof BlockComment + ) and + // comment text starts with `+build` + getText().regexpMatch("\\s*\\+build.*") + } +} diff --git a/ql/src/semmle/go/Concepts.qll b/ql/src/semmle/go/Concepts.qll new file mode 100644 index 00000000..ff004393 --- /dev/null +++ b/ql/src/semmle/go/Concepts.qll @@ -0,0 +1,595 @@ +/** + * Provides abstract classes representing generic concepts such as file system + * access or system command execution, for which individual framework libraries + * provide concrete subclasses. + */ + +import go +import semmle.go.dataflow.FunctionInputsAndOutputs + +/** + * A data-flow node that executes an operating system command, + * for instance by spawning a new process. + * + * Extends this class to refine existing API models. If you want to model new APIs, + * extend `SystemCommandExecution::Range` instead. + */ +class SystemCommandExecution extends DataFlow::Node { + SystemCommandExecution::Range self; + + SystemCommandExecution() { this = self } + + /** Gets the argument that specifies the command to be executed. */ + DataFlow::Node getCommandName() { result = self.getCommandName() } +} + +module SystemCommandExecution { + /** + * A data-flow node that executes an operating system command, + * for instance by spawning a new process. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `SystemCommandExecution` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the argument that specifies the command to be executed. */ + abstract DataFlow::Node getCommandName(); + } +} + +/** + * An instantiation of a template; that is, a call which fills out a template with data. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `TemplateInstantiation::Range` instead. + */ +class TemplateInstantiation extends DataFlow::Node { + TemplateInstantiation::Range self; + + TemplateInstantiation() { this = self } + + /** + * Gets the argument to this template instantiation that is the template being + * instantiated. + */ + DataFlow::Node getTemplateArgument() { result = self.getTemplateArgument() } + + /** + * Gets an argument to this template instantiation that is data being inserted + * into the template. + */ + DataFlow::Node getADataArgument() { result = self.getADataArgument() } +} + +module TemplateInstantiation { + /** + * An instantiation of a template; that is, a call which fills out a template with data. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `TemplateInstantiation` instead. + */ + abstract class Range extends DataFlow::Node { + /** + * Gets the argument to this template instantiation that is the template being + * instantiated. + */ + abstract DataFlow::Node getTemplateArgument(); + + /** + * Gets an argument to this template instantiation that is data being inserted + * into the template. + */ + abstract DataFlow::Node getADataArgument(); + } +} + +/** + * A data-flow node that performs a file system access, including reading and writing data, + * creating and deleting files and folders, checking and updating permissions, and so on. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `FileSystemAccess::Range` instead. + */ +class FileSystemAccess extends DataFlow::Node { + FileSystemAccess::Range self; + + FileSystemAccess() { this = self } + + /** Gets an argument to this file system access that is interpreted as a path. */ + DataFlow::Node getAPathArgument() { result = self.getAPathArgument() } +} + +module FileSystemAccess { + /** + * A data-flow node that performs a file system access, including reading and writing data, + * creating and deleting files and folders, checking and updating permissions, and so on. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `FileSystemAccess` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets an argument to this file system access that is interpreted as a path. */ + abstract DataFlow::Node getAPathArgument(); + } +} + +/** A function that escapes meta-characters to prevent injection attacks. */ +class EscapeFunction extends Function { + EscapeFunction::Range self; + + EscapeFunction() { this = self } + + /** + * The context that this function escapes for. + * + * Currently, this can be "js", "html", or "url". + */ + string kind() { result = self.kind() } +} + +module EscapeFunction { + /** + * A function that escapes meta-characters to prevent injection attacks. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `EscapeFunction' instead. + */ + abstract class Range extends Function { + /** + * The context that this function escapes for. + * + * Currently, this can be `js', `html', or `url'. + */ + abstract string kind(); + } +} + +/** + * A function that escapes a string so it can be safely included in a + * JavaScript string literal. + */ +class JsEscapeFunction extends EscapeFunction { + JsEscapeFunction() { self.kind() = "js" } +} + +/** + * A function that escapes a string so it can be safely included in an + * the body of an HTML element, for example, replacing `{}` in + * `

    {}

    `. + */ +class HtmlEscapeFunction extends EscapeFunction { + HtmlEscapeFunction() { self.kind() = "html" } +} + +/** + * A function that escapes a string so it can be safely included as part + * of a URL. + */ +class UrlEscapeFunction extends EscapeFunction { + UrlEscapeFunction() { self.kind() = "url" } +} + +/** + * A node whose value is interpreted as a part of a regular expression. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `RegexpPattern::Range` instead. + */ +class RegexpPattern extends DataFlow::Node { + RegexpPattern::Range self; + + RegexpPattern() { this = self } + + /** + * Gets the node where this pattern is parsed as a part of a regular + * expression. + */ + DataFlow::Node getAParse() { result = self.getAParse() } + + /** + * Gets this regexp pattern as a string. + */ + string getPattern() { result = self.getPattern() } + + /** + * Gets a use of this pattern, either as itself in an argument to a function or as a compiled + * regexp object. + */ + DataFlow::Node getAUse() { result = self.getAUse() } +} + +module RegexpPattern { + /** + * A node whose value is interpreted as a part of a regular expression. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `RegexpPattern' instead. + */ + abstract class Range extends DataFlow::Node { + /** + * Gets a node where the pattern of this node is parsed as a part of + * a regular expression. + */ + abstract DataFlow::Node getAParse(); + + /** + * Gets this regexp pattern as a string. + */ + abstract string getPattern(); + + /** + * Gets a use of this pattern, either as itself in an argument to a function or as a compiled + * regexp object. + */ + abstract DataFlow::Node getAUse(); + } +} + +/** + * A function that matches a regexp with a string or byte slice. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `RegexpMatchFunction::Range` instead. + */ +class RegexpMatchFunction extends Function { + RegexpMatchFunction::Range self; + + RegexpMatchFunction() { this = self } + + /** + * Gets the function input that is the regexp being matched. + */ + FunctionInput getRegexpArg() { result = self.getRegexpArg() } + + /** + * Gets the regexp pattern that is used in the call to this function `call`. + */ + RegexpPattern getRegexp(DataFlow::CallNode call) { + result.getAUse() = this.getRegexpArg().getNode(call) + } + + /** + * Gets the function input that is the string being matched against. + */ + FunctionInput getValue() { result = self.getValue() } + + /** + * Gets the function output that is the Boolean result of the match function. + */ + FunctionOutput getResult() { result = self.getResult() } +} + +module RegexpMatchFunction { + /* + * A function that matches a regexp with a string or byte slice. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `RegexpPattern' instead. + */ + + abstract class Range extends Function { + /** + * Gets the function input that is the regexp being matched. + */ + abstract FunctionInput getRegexpArg(); + + /** + * Gets the function input that is the string being matched against. + */ + abstract FunctionInput getValue(); + + /** + * Gets the Boolean result of the match function. + */ + abstract FunctionOutput getResult(); + } +} + +/** + * A function that uses a regexp to replace parts of a string or byte slice. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `RegexpReplaceFunction::Range` instead. + */ +class RegexpReplaceFunction extends Function { + RegexpReplaceFunction::Range self; + + RegexpReplaceFunction() { this = self } + + /** + * Gets the function input that is the regexp that matches text to replace. + */ + FunctionInput getRegexpArg() { result = self.getRegexpArg() } + + /** + * Gets the regexp pattern that is used to match patterns to replace in the call to this function + * `call`. + */ + RegexpPattern getRegexp(DataFlow::CallNode call) { + result.getAUse() = call.(DataFlow::MethodCallNode).getReceiver() + } + + /** + * Gets the function input corresponding to the source value, that is, the value that is having + * its contents replaced. + */ + FunctionInput getSource() { result = self.getSource() } + + /** + * Gets the function output corresponding to the result, that is, the value after replacement has + * occurred. + */ + FunctionOutput getResult() { result = self.getResult() } +} + +module RegexpReplaceFunction { + /** + * A function that uses a regexp to replace parts of a string or byte slice. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `RegexpReplaceFunction' instead. + */ + abstract class Range extends Function { + /** + * Gets the function input that is the regexp that matches text to replace. + */ + abstract FunctionInput getRegexpArg(); + + /** + * Gets the function input corresponding to the source value, that is, the value that is having + * its contents replaced. + */ + abstract FunctionInput getSource(); + + /** + * Gets the function output corresponding to the result, that is, the value after replacement + * has occurred. + */ + abstract FunctionOutput getResult(); + } +} + +module HTTP { + module ResponseWriter { + /** + * A variable that is an HTTP response writer. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `HTTP::ResponseWriter` instead. + */ + abstract class Range extends Variable { + /** Gets a data-flow node that represents this response writer. */ + DataFlow::Node getANode() { result = this.getARead().getASuccessor*() } + } + } + + /** + * A variable that is an HTTP response writer. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `HTTP::ResponseWriter::Range` instead. + */ + class ResponseWriter extends Variable { + ResponseWriter::Range self; + + ResponseWriter() { this = self } + + /** Gets the body that is written in this HTTP response. */ + ResponseBody getBody() { result.getResponseWriter() = this } + + /** Gets a header write that is written in this HTTP response. */ + HeaderWrite getAHeaderWrite() { result.getResponseWriter() = this } + + /** Gets a redirect that is sent in this HTTP response. */ + Redirect getARedirect() { result.getResponseWriter() = this } + + /** Gets a data-flow node that represents this response writer. */ + DataFlow::Node getANode() { result = self.getANode() } + } + + module HeaderWrite { + /** + * A data-flow node that represents a write to an HTTP header. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `HTTP::HeaderWrite` instead. + */ + abstract class Range extends DataFlow::ExprNode { + /** Gets the (lower-case) name of a header set by this definition. */ + string getHeaderName() { result = this.getName().getStringValue().toLowerCase() } + + /** Holds if this header write defines the header `header`. */ + predicate definesHeader(string header, string value) { + header = this.getName().getStringValue().toLowerCase() and + value = this.getValue().getStringValue() + } + + /** Gets the node representing the name of the header defined by this write. */ + abstract DataFlow::Node getName(); + + /** Gets the node representing the value of the header defined by this write. */ + abstract DataFlow::Node getValue(); + + /** Gets the response writer associated with this header write, if any. */ + abstract ResponseWriter getResponseWriter(); + } + } + + /* + * A data-flow node that represents a write to an HTTP header. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `HTTP::HeaderWrite::Range` instead. + */ + + class HeaderWrite extends DataFlow::ExprNode { + HeaderWrite::Range self; + + HeaderWrite() { this = self } + + /** Gets the (lower-case) name of a header set by this definition. */ + string getHeaderName() { result = self.getHeaderName() } + + /** Holds if this header write defines the header `header`. */ + predicate definesHeader(string header, string value) { self.definesHeader(header, value) } + + /** Gets the node representing the name of the header defined by this write. */ + DataFlow::Node getName() { result = self.getName() } + + /** Gets the node representing the value of the header defined by this write. */ + DataFlow::Node getValue() { result = self.getValue() } + + /** Gets the response writer associated with this header write, if any. */ + ResponseWriter getResponseWriter() { result = self.getResponseWriter() } + } + + /** A data-flow node whose value is written to an HTTP header. */ + class Header extends DataFlow::Node { + HeaderWrite hw; + + Header() { + this = hw.getName() + or + this = hw.getValue() + } + + /** Gets the response writer associated with this header write, if any. */ + ResponseWriter getResponseWriter() { result = hw.getResponseWriter() } + } + + /** A data-flow node whose value is written to the value of an HTTP header. */ + class HeaderValue extends Header { + HeaderValue() { this = hw.getValue() } + } + + /** A data-flow node whose value is written to the name of an HTTP header. */ + class HeaderName extends Header { + HeaderName() { this = hw.getName() } + } + + module RequestBody { + /** + * An expression representing a reader whose content is written to an HTTP request body. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `HTTP::RequestBody` instead. + */ + abstract class Range extends DataFlow::Node { } + } + + /** + * An expression representing a reader whose content is written to an HTTP request body. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `HTTP::RequestBody::Range` instead. + */ + class RequestBody extends DataFlow::Node { + RequestBody::Range self; + + RequestBody() { this = self } + } + + module ResponseBody { + /* + * An expression which is written to an HTTP response body. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `HTTP::ResponseBody` instead. + */ + + abstract class Range extends DataFlow::Node { + /** Gets the response writer associated with this header write, if any. */ + abstract ResponseWriter getResponseWriter(); + } + } + + /* + * An expression which is written to an HTTP response body. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `HTTP::ResponseBody::Range` instead. + */ + + class ResponseBody extends DataFlow::Node { + ResponseBody::Range self; + + ResponseBody() { this = self } + + /** Gets the response writer associated with this header write, if any. */ + ResponseWriter getResponseWriter() { result = self.getResponseWriter() } + } + + module Redirect { + /** + * An HTTP redirect. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `HTTP::Redirect` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets the data-flow node representing the URL being redirected to. */ + abstract DataFlow::Node getUrl(); + + /** Gets the response writer that this redirect is sent on. */ + abstract ResponseWriter getResponseWriter(); + } + + /** + * An assignment of the HTTP Location header, which indicates the location for a + * redirect. + */ + private class LocationHeaderSet extends Range, HeaderWrite { + LocationHeaderSet() { this.getHeaderName() = "location" } + + override DataFlow::Node getUrl() { result = this.getValue() } + + override ResponseWriter getResponseWriter() { result = HeaderWrite.super.getResponseWriter() } + } + } + + /** + * An HTTP redirect. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `HTTP::Redirect::Range` instead. + */ + class Redirect extends DataFlow::Node { + Redirect::Range self; + + Redirect() { this = self } + + /** Gets the data-flow node representing the URL being redirected to. */ + DataFlow::Node getUrl() { result = self.getUrl() } + + /** Gets the response writer that this redirect is sent on. */ + ResponseWriter getResponseWriter() { result = self.getResponseWriter() } + } +} + +/** + * A call to a logging mechanism. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `LoggerCall::Range` instead. + */ +class LoggerCall extends DataFlow::Node { + LoggerCall::Range self; + + LoggerCall() { this = self } + + /** Gets a node that is a part of the logged message. */ + DataFlow::Node getAMessageComponent() { result = self.getAMessageComponent() } +} + +module LoggerCall { + /** + * A call to a logging mechanism. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `LoggerCall` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets a node that is a part of the logged message. */ + abstract DataFlow::Node getAMessageComponent(); + } +} diff --git a/ql/src/semmle/go/Decls.qll b/ql/src/semmle/go/Decls.qll new file mode 100644 index 00000000..4ab2933d --- /dev/null +++ b/ql/src/semmle/go/Decls.qll @@ -0,0 +1,594 @@ +/** + * Provides classes for working with declarations. + */ + +import go + +/** + * A declaration. + */ +class Decl extends @decl, ExprParent, StmtParent { + /** + * Gets the kind of this declaration, which is an integer value representing the declaration's + * node type. + * + * Note that the mapping from node types to integer kinds is considered an implementation detail + * and subject to change without notice. + */ + int getKind() { decls(this, result, _, _) } + + /** + * Holds if the execution of this statement may produce observable side effects. + * + * Memory allocation is not considered an observable side effect. + */ + predicate mayHaveSideEffects() { none() } +} + +/** + * A bad declaration, that is, a declaration that cannot be parsed. + */ +class BadDecl extends @baddecl, Decl { + override string toString() { result = "bad declaration" } +} + +/** + * A generic declaration. + */ +class GenDecl extends @gendecl, Decl, Documentable { + /** Gets the `i`th declaration specifier in this declaration (0-based). */ + Spec getSpec(int i) { specs(result, _, this, i) } + + /** Gets a declaration specifier in this declaration. */ + Spec getASpec() { result = getSpec(_) } + + /** Gets the number of declaration specifiers in this declaration. */ + int getNumSpec() { result = count(getASpec()) } + + override predicate mayHaveSideEffects() { getASpec().mayHaveSideEffects() } +} + +/** + * An import declaration. + */ +class ImportDecl extends @importdecl, GenDecl { + override string toString() { result = "import declaration" } +} + +/** + * A constant declaration. + */ +class ConstDecl extends @constdecl, GenDecl { + override string toString() { result = "constant declaration" } +} + +/** + * A type declaration. + */ +class TypeDecl extends @typedecl, GenDecl { + override string toString() { result = "type declaration" } +} + +/** + * A variable declaration. + */ +class VarDecl extends @vardecl, GenDecl { + override string toString() { result = "variable declaration" } +} + +/** + * A function definition, that is, either a function declaration or + * a function literal. + */ +class FuncDef extends @funcdef, StmtParent, ExprParent { + /** Gets the body of the defined function, if any. */ + BlockStmt getBody() { none() } + + /** Gets the name of the defined function, if any. */ + string getName() { none() } + + /** Gets the expression denoting the type of this function. */ + FuncTypeExpr getTypeExpr() { none() } + + /** Gets the type of this function. */ + SignatureType getType() { none() } + + /** Gets the scope induced by this function. */ + FunctionScope getScope() { result.getFunction() = this } + + /** Gets a `defer` statement in this function. */ + DeferStmt getADeferStmt() { result.getEnclosingFunction() = this } + + /** Gets the `i`th result variable of this function. */ + ResultVariable getResultVar(int i) { + result = rank[i + 1](ResultVariable res, int j, int k | + res.getDeclaration() = getTypeExpr().getResultDecl(j).getNameExpr(k) + | + res order by j, k + ) + } + + /** Gets a result variable of this function. */ + ResultVariable getAResultVar() { + result.getDeclaration() = getTypeExpr().getAResultDecl().getANameExpr() + } + + /** + * Gets the `i`th parameter of this function. + */ + Parameter getParameter(int i) { + result = rank[i + 1](Parameter parm, int j, int k | + parm.getDeclaration() = getTypeExpr().getParameterDecl(j).getNameExpr(k) + | + parm order by j, k + ) + } + + /** + * Gets a parameter of this function. + */ + Parameter getAParameter() { + result.getDeclaration() = getTypeExpr().getAParameterDecl().getNameExpr(_) + } +} + +/** + * A function declaration. + */ +class FuncDecl extends @funcdecl, Decl, Documentable, FuncDef { + /** Gets the identifier denoting the name of this function. */ + Ident getNameExpr() { result = getChildExpr(0) } + + override string getName() { result = getNameExpr().getName() } + + override FuncTypeExpr getTypeExpr() { result = getChildExpr(1) } + + override SignatureType getType() { result = getNameExpr().getType() } + + /** Gets the body of this function, if any. */ + override BlockStmt getBody() { result = getChildStmt(2) } + + /** Gets the function declared by this function declaration. */ + DeclaredFunction getFunction() { this = result.getDecl() } + + /** Gets a (possibly virtual) call to this function. */ + CallExpr getACall() { this = result.getACallee() } + + override string toString() { result = "function declaration" } +} + +/** + * A method declaration. + */ +class MethodDecl extends FuncDecl { + ReceiverDecl recv; + + MethodDecl() { recv.getFunction() = this } + + /** + * Gets the receiver declaration of this method. + * + * For example, the receiver declaration of + * + * ``` + * func (p *Rectangle) Area() float64 { ... } + * ``` + * + * is `p *Rectangle`. + */ + ReceiverDecl getReceiverDecl() { result = recv } + + /** + * Gets the receiver type of this method. + * + * For example, the receiver type of + * + * ``` + * func (p *Rectangle) Area() float64 { ... } + * ``` + * + * is `*Rectangle`. + */ + Type getReceiverType() { result = getReceiverDecl().getType() } + + /** + * Gets the receiver base type of this method. + * + * For example, the receiver base type of + * + * ``` + * func (p *Rectangle) Area() float64 { ... } + * ``` + * + * is `Rectangle`. + */ + NamedType getReceiverBaseType() { + result = getReceiverType() or + result = getReceiverType().(PointerType).getBaseType() + } + + /** + * Gets the receiver variable of this method. + * + * For example, the receiver variable of + * + * ``` + * func (p *Rectangle) Area() float64 { ... } + * ``` + * + * is the variable `p`. + */ + ReceiverVariable getReceiver() { result.getFunction() = this } +} + +/** + * A declaration specifier. + */ +class Spec extends @spec, ExprParent, Documentable { + /** Gets the declaration to which this specifier belongs */ + Decl getParentDecl() { specs(this, _, result, _) } + + /** + * Gets the kind of this specifier, which is an integer value representing the specifier's + * node type. + * + * Note that the mapping from node types to integer kinds is considered an implementation detail + * and subject to change without notice. + */ + int getKind() { specs(this, result, _, _) } + + /** + * Holds if the execution of this specifier may produce observable side effects. + * + * Memory allocation is not considered an observable side effect. + */ + predicate mayHaveSideEffects() { none() } +} + +/** + * An import specifier. + */ +class ImportSpec extends @importspec, Spec { + /** Gets the identifier denoting the imported name. */ + Ident getNameExpr() { result = getChildExpr(0) } + + /** Gets the imported name. */ + string getName() { result = getNameExpr().getName() } + + /** Gets the string literal denoting the imported path. */ + StringLit getPathExpr() { result = getChildExpr(1) } + + /** Gets the imported path. */ + string getPath() { result = getPathExpr().getValue() } + + override string toString() { result = "import specifier" } +} + +/** + * A constant or variable declaration specifier. + */ +class ValueSpec extends @valuespec, Spec { + /** Gets the identifier denoting the `i`th name declared by this specifier (0-based). */ + Ident getNameExpr(int i) { + i >= 0 and + result = getChildExpr(-(i + 1)) + } + + /** Holds if this specifier is a part of a constant declaration. */ + predicate isConstSpec() { this.getParentDecl() instanceof ConstDecl } + + /** Gets an identifier denoting a name declared by this specifier. */ + Ident getANameExpr() { result = getNameExpr(_) } + + /** Gets the `i`th name declared by this specifier (0-based). */ + string getName(int i) { result = getNameExpr(i).getName() } + + /** Gets a name declared by this specifier. */ + string getAName() { result = getName(_) } + + /** Gets the number of names declared by this specifier. */ + int getNumName() { result = count(getANameExpr()) } + + /** Gets the expression denoting the type of the symbols declared by this specifier. */ + Expr getTypeExpr() { result = getChildExpr(0) } + + /** Gets the `i`th initializer of this specifier (0-based). */ + Expr getInit(int i) { + i >= 0 and + result = getChildExpr(i + 1) + } + + /** Gets an initializer of this specifier. */ + Expr getAnInit() { result = getInit(_) } + + /** Gets the number of initializers of this specifier. */ + int getNumInit() { result = count(getAnInit()) } + + /** Gets the unique initializer of this specifier, if there is only one. */ + Expr getInit() { getNumInit() = 1 and result = getInit(0) } + + /** + * Gets the specifier that contains the initializers for this specifier. + * If this valuespec has initializers, the result is itself. Otherwise, it is the + * last specifier declared before this one that has initializers. + */ + private ValueSpec getEffectiveSpec() { + (exists(this.getAnInit()) or not this.isConstSpec()) and + result = this + or + not exists(this.getAnInit()) and + exists(ConstDecl decl, int idx | + decl = this.getParentDecl() and + decl.getSpec(idx) = this + | + result = decl.getSpec(idx - 1).(ValueSpec).getEffectiveSpec() + ) + } + + /** + * Gets the `i`th effective initializer of this specifier, that is, the expression + * that the `i`th name will get initialized to. This is the same as `getInit` + * if it exists, or `getInit` on the last specifier in the declaration that this + * is a child of. + */ + private Expr getEffectiveInit(int i) { result = this.getEffectiveSpec().getInit(i) } + + /** Holds if this specifier initializes `name` to the value of `init`. */ + predicate initializes(string name, Expr init) { + exists(int i | + name = getName(i) and + init = getEffectiveInit(i) + ) + } + + override predicate mayHaveSideEffects() { getAnInit().mayHaveSideEffects() } + + override string toString() { result = "value declaration specifier" } +} + +/** + * A type declaration specifier. + */ +class TypeSpec extends @typespec, Spec { + /** Gets the identifier denoting the name of the declared type. */ + Ident getNameExpr() { result = getChildExpr(0) } + + /** Gets the name of the declared type. */ + string getName() { result = getNameExpr().getName() } + + /** Gets the expression denoting the underlying type to which the newly declared type is aliased. */ + Expr getTypeExpr() { result = getChildExpr(1) } + + override string toString() { result = "type declaration specifier" } +} + +/** + * A field declaration in a struct type. + */ +class FieldDecl extends @field, Documentable, ExprParent { + FieldDecl() { fields(this, any(StructTypeExpr st), _) } + + /** + * Gets the expression representing the type of the fields declared in this declaration. + */ + Expr getTypeExpr() { result = getChildExpr(0) } + + /** + * Gets the type of the fields declared in this declaration. + */ + Type getType() { result = getTypeExpr().getType() } + + /** + * Gets the expression representing the name of the `i`th field declared in this declaration + * (0-based). + */ + Expr getNameExpr(int i) { + i >= 0 and + result = getChildExpr(i + 1) + } + + /** Gets the tag expression of this field declaration, if any. */ + Expr getTag() { result = getChildExpr(-1) } + + /** Gets the struct type expression to which this field declaration belongs. */ + StructTypeExpr getDeclaringStructTypeExpr() { fields(this, result, _) } + + /** Gets the struct type to which this field declaration belongs. */ + StructType getDeclaringType() { result = getDeclaringStructTypeExpr().getType() } + + override string toString() { result = "field declaration" } +} + +/** + * An embedded field declaration in a struct. + */ +class EmbeddedFieldDecl extends FieldDecl { + EmbeddedFieldDecl() { not exists(this.getNameExpr(_)) } +} + +/** + * A parameter declaration. + */ +class ParameterDecl extends @field, Documentable, ExprParent { + ParameterDecl() { + exists(int i | + fields(this, any(FuncTypeExpr ft), i) and + i >= 0 + ) + } + + /** + * Gets the function type expression to which this parameter declaration belongs. + */ + FuncTypeExpr getFunctionTypeExpr() { fields(this, result, _) } + + /** + * Gets the function to which this parameter declaration belongs. + */ + FuncDef getFunction() { result.getTypeExpr() = getFunctionTypeExpr() } + + /** + * Gets the index of this parameter declarations among all parameter declarations of + * its associated function type. + */ + int getIndex() { fields(this, _, result) } + + /** + * Gets the expression representing the type of the parameters declared in this declaration. + */ + Expr getTypeExpr() { result = getChildExpr(0) } + + /** + * Gets the type of the parameters declared in this declaration. + */ + Type getType() { result = getTypeExpr().getType() } + + /** + * Gets the expression representing the name of the `i`th parameter declared in this declaration + * (0-based). + */ + Expr getNameExpr(int i) { + i >= 0 and + result = getChildExpr(i + 1) + } + + override string toString() { result = "parameter declaration" } +} + +/** + * A receiver declaration in a function declaration. + */ +class ReceiverDecl extends @field, Documentable, ExprParent { + ReceiverDecl() { fields(this, any(FuncDecl fd), -1) } + + /** + * Gets the function declaration to which this receiver belongs. + */ + FuncDecl getFunction() { fields(this, result, _) } + + /** + * Gets the expression representing the type of the receiver declared in this declaration. + */ + Expr getTypeExpr() { result = getChildExpr(0) } + + /** + * Gets the type of the receiver declared in this declaration. + */ + Type getType() { result = getTypeExpr().getType() } + + /** + * Gets the expression representing the name of the receiver declared in this declaration. + */ + Expr getNameExpr() { result = getChildExpr(1) } + + override string toString() { result = "receiver declaration" } +} + +/** + * A result variable declaration. + */ +class ResultVariableDecl extends @field, Documentable, ExprParent { + ResultVariableDecl() { + exists(int i | + fields(this, any(FuncTypeExpr ft), i) and + i < 0 + ) + } + + /** + * Gets the expression representing the type of the result variables declared in this declaration. + */ + Expr getTypeExpr() { result = getChildExpr(0) } + + /** + * Gets the type of the result variables declared in this declaration. + */ + Type getType() { result = getTypeExpr().getType() } + + /** + * Gets the expression representing the name of the `i`th result variable declared in this declaration + * (0-based). + */ + Expr getNameExpr(int i) { + i >= 0 and + result = getChildExpr(i + 1) + } + + /** + * Gets an expression representing the name of a result variable declared in this declaration. + */ + Expr getANameExpr() { + result = getNameExpr(_) + } + + /** + * Gets the function type expression to which this result variable declaration belongs. + */ + FuncTypeExpr getFunctionTypeExpr() { fields(this, result, _) } + + /** + * Gets the index of this result variable declaration among all result variable declarations of + * its associated function type. + */ + int getIndex() { fields(this, _, -(result + 1)) } + + override string toString() { result = "result variable declaration" } +} + +/** + * A method or embedding specification in an interface type expression. + */ +class InterfaceMemberSpec extends @field, Documentable, ExprParent { + InterfaceTypeExpr ite; + + int idx; + + InterfaceMemberSpec() { fields(this, ite, idx) } + + /** + * Gets the interface type expression to which this member specification belongs. + */ + InterfaceTypeExpr getInterfaceTypeExpr() { result = ite } + + /** + * Gets the index of this member specification among all member specifications of + * its associated interface type expression. + */ + int getIndex() { result = idx } + + /** + * Gets the expression representing the type of the method or embedding declared in + * this specification. + */ + Expr getTypeExpr() { result = getChildExpr(0) } + + /** + * Gets the type of the method or embedding declared in this specification. + */ + Type getType() { result = getTypeExpr().getType() } +} + +/** + * A method specification in an interface. + */ +class MethodSpec extends InterfaceMemberSpec { + Expr name; + + MethodSpec() { name = getChildExpr(1) } + + /** + * Gets the expression representing the name of the method declared in this specification. + */ + Expr getNameExpr() { result = name } + + override string toString() { result = "method declaration" } +} + +/** + * An embedding specification in an interface. + */ +class EmbeddingSpec extends InterfaceMemberSpec { + EmbeddingSpec() { not exists(getChildExpr(1)) } + + override string toString() { result = "interface embedding" } +} diff --git a/ql/src/semmle/go/Expr.qll b/ql/src/semmle/go/Expr.qll new file mode 100644 index 00000000..7b9d84f4 --- /dev/null +++ b/ql/src/semmle/go/Expr.qll @@ -0,0 +1,1282 @@ +/** + * Provides classes for working with expressions. + */ + +import go + +/** + * An expression. + */ +class Expr extends @expr, ExprParent { + /** + * Gets the kind of this expression, which is an integer value representing the expression's + * node type. + * + * Note that the mapping from node types to integer kinds is considered an implementation detail + * and subject to change without notice. + */ + int getKind() { exprs(this, result, _, _) } + + /** Gets this expression, with any surrounding parentheses removed. */ + Expr stripParens() { result = this } + + /** + * Holds if this expression is constant, that is, if its value is determined at + * compile-time. + */ + predicate isConst() { constvalues(this, _, _) } + + /** + * Gets the boolean value this expression evalutes to, if any. + */ + boolean getBoolValue() { + this.getType().getUnderlyingType() instanceof BoolType and + exists(string val | constvalues(this, val, _) | + val = "true" and result = true + or + val = "false" and result = false + ) + } + + /** Gets the floating-point value this expression evaluates to, if any. */ + float getFloatValue() { + this.getType().getUnderlyingType() instanceof FloatType and + exists(string val | constvalues(this, val, _) | result = val.toFloat()) + } + + /** + * Gets the integer value this expression evaluates to, if any. + * + * Note that this does not have a result if the value is too large to fit in a + * 32-bit signed integer type. + */ + int getIntValue() { + this.getType().getUnderlyingType() instanceof IntegerType and + exists(string val | constvalues(this, val, _) | result = val.toInt()) + } + + /** Gets either `getFloatValue` or `getIntValue`. */ + float getNumericValue() { result = this.getFloatValue() or result = this.getIntValue() } + + /** + * Holds if the complex value this expression evaluates to has real part `real` and imaginary + * part `imag`. + */ + predicate hasComplexValue(float real, float imag) { + this.getType().getUnderlyingType() instanceof ComplexType and + exists(string val | constvalues(this, val, _) | + exists(string cmplxre | + cmplxre = "^\\((.+) \\+ (.+)i\\)$" and + real = val.regexpCapture(cmplxre, 1).toFloat() and + imag = val.regexpCapture(cmplxre, 2).toFloat() + ) + ) + } + + /** Gets the string value this expression evaluates to, if any. */ + string getStringValue() { + this.getType().getUnderlyingType() instanceof StringType and + constvalues(this, result, _) + } + + /** + * Gets the string representation of the exact value this expression + * evaluates to, if any. + * + * For example, for the constant 3.141592653589793238462, this will + * result in 1570796326794896619231/500000000000000000000 + */ + string getExactValue() { constvalues(this, _, result) } + + /** + * Holds if this expression has a constant value which is guaranteed not to depend on the + * platform where it is evaluated. + * + * This is a conservative approximation, that is, the predicate may fail to hold for expressions + * whose value is platform independent, but it will never hold for expressions whose value is not + * platform independent. + * + * Examples of platform-dependent constants include constants declared in files with build + * constraints, the value of `runtime.GOOS`, and the return value of `unsafe.Sizeof`. + */ + predicate isPlatformIndependentConstant() { none() } + + /** Gets the type of this expression. */ + Type getType() { type_of(this, result) } + + /** + * Gets the global value number of this expression. + * + * Expressions with the same global value number are guaranteed to have the same value at runtime. + * The converse does not hold in general, that is, expressions with different global value numbers + * may still have the same value at runtime. + */ + GVN getGlobalValueNumber() { result = globalValueNumber(DataFlow::exprNode(this)) } + + /** + * Holds if this expression may have observable side effects of its own (that is, independent + * of whether its sub-expressions may have side effects). + * + * Memory allocation is not considered an observable side effect. + */ + predicate mayHaveOwnSideEffects() { none() } + + /** + * Holds if the evaluation of this expression may produce observable side effects. + * + * Memory allocation is not considered an observable side effect. + */ + predicate mayHaveSideEffects() { mayHaveOwnSideEffects() or getAChildExpr().mayHaveSideEffects() } + + override string toString() { result = "expression" } +} + +/** + * A bad expression, that is, an expression that could not be parsed. + */ +class BadExpr extends @badexpr, Expr { + override string toString() { result = "bad expression" } +} + +/** + * An identifier. + */ +class Ident extends @ident, Expr { + /** Gets the name of this identifier. */ + string getName() { literals(this, result, _) } + + /** Holds if this identifier is a use of `e`. */ + predicate uses(Entity e) { uses(this, e) } + + /** Holds if this identifier is a definition or declaration of `e` */ + predicate declares(Entity e) { defs(this, e) } + + /** Holds if this identifier refers to (that is, uses, defines or declares) `e`. */ + predicate refersTo(Entity e) { uses(e) or declares(e) } + + override string toString() { result = getName() } +} + +/** + * The blank identifier `_`. + */ +class BlankIdent extends Ident { + BlankIdent() { getName() = "_" } +} + +/** + * An ellipsis expression, representing either the `...` type in a parameter list or + * the `...` length in an array type. + */ +class Ellipsis extends @ellipsis, Expr { + /** Gets the operand of this ellipsis expression. */ + Expr getOperand() { result = getChildExpr(0) } + + override string toString() { result = "..." } +} + +/** + * A literal expression of basic type. + */ +class BasicLit extends @basiclit, Expr { + /** Gets the value of this literal expressed as a string. */ + string getValue() { literals(this, result, _) } + + /** Gets the raw program text corresponding to this literal. */ + string getText() { literals(this, _, result) } + + override predicate isConst() { + // override to make sure literals are always considered constants even if we did not get + // information about constant values from the extractor (for example due to missing + // type information) + any() + } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = getText() } +} + +/** + * An integer literal. + */ +class IntLit extends @intlit, BasicLit { } + +/** + * A floating-point literal. + */ +class FloatLit extends @floatlit, BasicLit { } + +/** + * An imaginary literal. + */ +class ImagLit extends @imaglit, BasicLit { } + +/** + * A character literal. + */ +class CharLit extends @charlit, BasicLit { } + +/** + * A string literal. + */ +class StringLit extends @stringlit, BasicLit { } + +/** + * A function literal. + */ +class FuncLit extends @funclit, Expr, StmtParent, FuncDef { + override FuncTypeExpr getTypeExpr() { result = getChildExpr(0) } + + override SignatureType getType() { result = Expr.super.getType() } + + /** Gets the body of this function literal. */ + override BlockStmt getBody() { result = getChildStmt(1) } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "function literal" } +} + +/** + * A composite literal + */ +class CompositeLit extends @compositelit, Expr { + /** Gets the expression representing the type of this composite literal. */ + Expr getTypeExpr() { result = getChildExpr(0) } + + /** Gets the `i`th element of this composite literal (0-based). */ + Expr getElement(int i) { + i >= 0 and + result = getChildExpr(i + 1) + } + + /** Gets an element of this composite literal. */ + Expr getAnElement() { result = getElement(_) } + + /** Gets the number of elements in this composite literal. */ + int getNumElement() { result = count(getAnElement()) } + + /** + * Gets the `i`th key expression in this literal. + * + * If the `i`th element of this literal has no key, this predicate is undefined for `i`. + */ + Expr getKey(int i) { result = getElement(i).(KeyValueExpr).getKey() } + + /** + * Gets the `i`th value expression in this literal. + */ + Expr getValue(int i) { + exists(Expr elt | elt = getElement(i) | + result = elt.(KeyValueExpr).getValue() + or + not elt instanceof KeyValueExpr and result = elt + ) + } + + override string toString() { result = "composite literal" } +} + +/** + * A map literal. + */ +class MapLit extends CompositeLit { + MapType mt; + + MapLit() { mt = getType().getUnderlyingType() } + + /** Gets the key type of this literal. */ + Type getKeyType() { result = mt.getKeyType() } + + /** Gets the value type of this literal. */ + Type getValueType() { result = mt.getValueType() } +} + +/** + * A struct literal. + */ +class StructLit extends CompositeLit { + StructType st; + + StructLit() { st = getType().getUnderlyingType() } + + StructType getStructType() { result = st } +} + +/** + * A parenthesized expression. + */ +class ParenExpr extends @parenexpr, Expr { + /** Gets the expression between parentheses. */ + Expr getExpression() { result = getChildExpr(0) } + + override Expr stripParens() { result = getExpression().stripParens() } + + override predicate isPlatformIndependentConstant() { getExpression().isPlatformIndependentConstant() } + + override string toString() { result = "(...)" } +} + +/** + * A selector expression, that is, a base expression followed by a selector. + */ +class SelectorExpr extends @selectorexpr, Expr { + /** Gets the base of this selector expression. */ + Expr getBase() { result = getChildExpr(0) } + + /** Gets the selector of this selector expression. */ + Ident getSelector() { result = getChildExpr(1) } + + /** Holds if this selector is a use of `e`. */ + predicate uses(Entity e) { getSelector().uses(e) } + + /** Holds if this selector is a definition of `e` */ + predicate declares(Entity e) { getSelector().declares(e) } + + /** Holds if this selector refers to (that is, uses, defines or declares) `e`. */ + predicate refersTo(Entity e) { getSelector().refersTo(e) } + + override predicate mayHaveOwnSideEffects() { any() } + + override string toString() { result = "selection of " + getSelector() } +} + +/** + * An index expression, that is, a base expression followed by an index. + */ +class IndexExpr extends @indexexpr, Expr { + /** Gets the base of this index expression. */ + Expr getBase() { result = getChildExpr(0) } + + /** Gets the index of this index expression. */ + Expr getIndex() { result = getChildExpr(1) } + + override predicate mayHaveOwnSideEffects() { any() } + + override string toString() { result = "index expression" } +} + +/** + * A slice expression, that is, a base expression followed by slice indices. + */ +class SliceExpr extends @sliceexpr, Expr { + /** Gets the base of this slice expression. */ + Expr getBase() { result = getChildExpr(0) } + + /** Gets the lower bound of this slice expression. */ + Expr getLow() { result = getChildExpr(1) } + + /** Gets the upper bound of this slice expression. */ + Expr getHigh() { result = getChildExpr(2) } + + /** Gets the maximum of this slice expression, if any. */ + Expr getMax() { result = getChildExpr(3) } + + override string toString() { result = "slice expression" } +} + +/** + * A type assertion expression. + */ +class TypeAssertExpr extends @typeassertexpr, Expr { + /** Gets the base expression whose type is being asserted. */ + Expr getExpression() { result = getChildExpr(0) } + + /** Gets the expression representing the asserted type. */ + Expr getTypeExpr() { result = getChildExpr(1) } + + override predicate mayHaveOwnSideEffects() { any() } + + override predicate isPlatformIndependentConstant() { getExpression().isPlatformIndependentConstant() } + + override string toString() { result = "type assertion" } +} + +/** + * An expression that syntactically could either be a function call or a type + * conversion expression. + * + * In most cases, the subclasses `CallExpr` and `ConversionExpr` should be used + * instead. + */ +class CallOrConversionExpr extends @callorconversionexpr, Expr { } + +/** + * A type conversion expression. + */ +class ConversionExpr extends CallOrConversionExpr { + ConversionExpr() { isTypeExprBottomUp(getChildExpr(0)) } + + /** Gets the type expression representing the target type of the conversion. */ + Expr getTypeExpr() { result = getChildExpr(0) } + + /** Gets the operand of the type conversion. */ + Expr getOperand() { result = getChildExpr(1) } + + override predicate isPlatformIndependentConstant() { getOperand().isPlatformIndependentConstant() } + + override string toString() { result = "type conversion" } +} + +/** + * A function call expression. + * + * On snapshots with incomplete type information, type conversions may be misclassified + * as function call expressions. + */ +class CallExpr extends CallOrConversionExpr { + CallExpr() { exists(Expr callee | callee = getChildExpr(0) | not isTypeExprBottomUp(callee)) } + + /** Gets the expression representing the function being called. */ + Expr getCalleeExpr() { result = getChildExpr(0) } + + /** Holds if this call is of the form `base.method(...)`. */ + predicate calls(Expr base, string method) { + exists(SelectorExpr callee | callee = getCalleeExpr().stripParens() | + callee.getBase() = base and + method = callee.getSelector().getName() + ) + } + + /** + * Gets the qualifier of this call if it can be determined syntactically. + * + * For example, in the call `fmt.Println("hello")`, the qualifier is `fmt`. + */ + Expr getQualifier() { calls(result, _) } + + /** Gets the `i`th argument expression of this call (0-based). */ + Expr getArgument(int i) { + i >= 0 and + result = getChildExpr(i + 1) + } + + /** Gets an argument expression of this call. */ + Expr getAnArgument() { result = getArgument(_) } + + /** Gets the number of argument expressions of this call. */ + int getNumArgument() { result = count(getAnArgument()) } + + /** Gets the name of the invoked function or method. */ + string getCalleeName() { + result = getCalleeExpr().stripParens().(Ident).getName() or + calls(_, result) + } + + /** Gets the declared target of this call. */ + Function getTarget() { this = result.getACallExpr() } + + /** + * Gets the declaration of a possible target of this call. + * + * For non-virtual calls, there is at most one possible call target (but there may be none if the + * target has no declaration). + * + * For virtual calls, we look up possible targets in all types that implement the receiver interface + * type. + */ + FuncDecl getACallee() { + result = getTarget().(DeclaredFunction).getDecl() + or + exists(SelectorExpr sel, InterfaceType declaredRecv, Type actualRecv | + sel = getCalleeExpr().stripParens() and + declaredRecv = sel.getBase().getType().getUnderlyingType() and + actualRecv.implements(declaredRecv) + | + result = actualRecv + .(PointerType) + .getBaseType() + .(NamedType) + .getMethodDecl(sel.getSelector().getName()) + ) + } + + override predicate mayHaveOwnSideEffects() { + getTarget().mayHaveSideEffects() or + not exists(getTarget()) + } + + override string toString() { + result = "call to " + getCalleeName() + or + not exists(getCalleeName()) and + result = "function call" + } +} + +/** + * A star expression. + */ +class StarExpr extends @starexpr, Expr { + /** Gets the base expression of this star expression. */ + Expr getBase() { result = getChildExpr(0) } + + override predicate mayHaveOwnSideEffects() { any() } + + override string toString() { result = "star expression" } +} + +/** + * A key-value pair in a composite literal. + */ +class KeyValueExpr extends @keyvalueexpr, Expr { + /** Gets the key expression of this key-value pair. */ + Expr getKey() { result = getChildExpr(0) } + + /** Gets the value expression of this key-value pair. */ + Expr getValue() { result = getChildExpr(1) } + + /** Gets the composite literal to which this key-value pair belongs. */ + CompositeLit getLiteral() { this = result.getElement(_) } + + override string toString() { result = "key-value pair" } +} + +/** + * An expression representing an array type. + */ +class ArrayTypeExpr extends @arraytypeexpr, Expr { + /** Gets the length expression of this array type. */ + Expr getLength() { result = getChildExpr(0) } + + /** Gets the expression representing the element type of this array type. */ + Expr getElement() { result = getChildExpr(1) } + + override string toString() { result = "array type" } +} + +/** + * An expression representing a struct type. + */ +class StructTypeExpr extends @structtypeexpr, Expr { + /** Gets the `i`th field declared in this struct type expression (0-based). */ + FieldDecl getField(int i) { fields(result, this, i) } + + /** Gets a field declared in this struct type expression. */ + FieldDecl getAField() { result = getField(_) } + + /** Gets the number of fields declared in this struct type expression. */ + int getNumField() { result = count(getAField()) } + + override string toString() { result = "struct type" } +} + +/** + * An expression representing a function type. + */ +class FuncTypeExpr extends @functypeexpr, Expr, ScopeNode { + /** Gets the `i`th parameter of this function type (0-based). */ + ParameterDecl getParameterDecl(int i) { + result.getFunctionTypeExpr() = this and + result.getIndex() = i + } + + /** Gets a parameter of this function type. */ + ParameterDecl getAParameterDecl() { result = getParameterDecl(_) } + + /** Gets the number of parameters of this function type. */ + int getNumParameter() { result = count(getAParameterDecl()) } + + /** Gets the `i`th result of this function type (0-based). */ + ResultVariableDecl getResultDecl(int i) { + result.getFunctionTypeExpr() = this and + result.getIndex() = i + } + + /** Gets a result of this function type. */ + ResultVariableDecl getAResultDecl() { result = getResultDecl(_) } + + /** Gets the number of results of this function type. */ + int getNumResult() { result = count(getAResultDecl()) } + + /** Gets the result of this function type, if there is only one. */ + ResultVariableDecl getResultDecl() { getNumResult() = 1 and result = getAResultDecl() } + + override string toString() { result = "function type" } +} + +/** + * An expression representing an interface type. + */ +class InterfaceTypeExpr extends @interfacetypeexpr, Expr { + /** Gets the `i`th method specification of this interface type. */ + MethodSpec getMethod(int i) { + result.getInterfaceTypeExpr() = this and + result.getIndex() = i + } + + /** Gets a method of this interface type. */ + MethodSpec getAMethod() { result = getMethod(_) } + + /** Gets the number of methods of this interface type. */ + int getNumMethod() { result = count(getAMethod()) } + + override string toString() { result = "interface type" } +} + +/** + * An expression representing a map type. + */ +class MapTypeExpr extends @maptypeexpr, Expr { + /** Gets the expression representing the key type of this map type. */ + Expr getKeyTypeExpr() { result = getChildExpr(0) } + + /** Gets the key type of this map type. */ + Type getKeyType() { result = getKeyTypeExpr().getType() } + + /** Gets the expression representing the value type of this map type. */ + Expr getValueTypeExpr() { result = getChildExpr(1) } + + /** Gets the value type of this map type. */ + Type getValueType() { result = getValueTypeExpr().getType() } + + override string toString() { result = "map type" } +} + +/** + * An expression with a (unary or binary) operator. + */ +class OperatorExpr extends @operatorexpr, Expr { + /** Gets the operator of this expression. */ + string getOperator() { none() } +} + +/** + * An expression with an arithmetic operator like `-` or `/`. + */ +class ArithmeticExpr extends @arithmeticexpr, OperatorExpr { } + +/** + * An expression with a logical operator like `!` or `&&`. + */ +class LogicalExpr extends @logicalexpr, OperatorExpr { } + +/** + * An expression with a bitwise operator such as `^` or `|`. + */ +class BitwiseExpr extends @bitwiseexpr, OperatorExpr { } + +/** + * An expression with a unary operator. + */ +class UnaryExpr extends @unaryexpr, OperatorExpr { + /** Gets the operand of this unary expression. */ + Expr getOperand() { result = getChildExpr(0) } + + override predicate isPlatformIndependentConstant() { getOperand().isPlatformIndependentConstant() } + + override string toString() { result = getOperator() + "..." } +} + +/** + * An expression with a unary arithmetic operator, that is, unary `-` or `+`. + */ +class ArithmeticUnaryExpr extends @arithmeticunaryexpr, ArithmeticExpr, UnaryExpr { } + +/** + * An expression with a unary logical operator, that is, `!`. + */ +class LogicalUnaryExpr extends @logicalunaryexpr, LogicalExpr, UnaryExpr { } + +/** + * An expression with a unary bitwise operator, that is, `^`. + */ +class BitwiseUnaryExpr extends @bitwiseunaryexpr, BitwiseExpr, UnaryExpr { } + +/** + * A unary plus expression using `+`. + */ +class PlusExpr extends @plusexpr, ArithmeticUnaryExpr { + override string getOperator() { result = "+" } +} + +/** + * A unary minus expression using `-`. + */ +class MinusExpr extends @minusexpr, ArithmeticUnaryExpr { + override string getOperator() { result = "-" } +} + +/** + * A unary "not" expression using `!`. + */ +class NotExpr extends @notexpr, LogicalUnaryExpr { + override string getOperator() { result = "!" } +} + +/** + * A unary complement expression using `^`. + */ +class ComplementExpr extends @complementexpr, BitwiseUnaryExpr { + override string getOperator() { result = "^" } +} + +/** + * A unary pointer-dereference expression using `*`. + */ +class DerefExpr extends @derefexpr, UnaryExpr { + override predicate mayHaveOwnSideEffects() { any() } + + override string getOperator() { result = "*" } +} + +/** + * A unary address-of expression using `&`. + */ +class AddressExpr extends @addressexpr, UnaryExpr { + override predicate mayHaveOwnSideEffects() { any() } + + override string getOperator() { result = "&" } +} + +/** + * A unary arrow expression using `<-`. + */ +class ArrowExpr extends @arrowexpr, UnaryExpr { + override predicate mayHaveOwnSideEffects() { any() } + + override string getOperator() { result = "<-" } +} + +/** + * A binary expression. + */ +class BinaryExpr extends @binaryexpr, OperatorExpr { + /** Gets the left operand of this binary expression. */ + Expr getLeftOperand() { result = getChildExpr(0) } + + /** Gets the right operand of this binary expression. */ + Expr getRightOperand() { result = getChildExpr(1) } + + /** Gets an operand of this binary expression. */ + Expr getAnOperand() { result = getChildExpr([0 .. 1]) } + + /** Holds if `e` and `f` (in either order) are the two operands of this binary expression. */ + predicate hasOperands(Expr e, Expr f) { + e = getAnOperand() and + f = getAnOperand() and + e != f + } + + override predicate isPlatformIndependentConstant() { + getLeftOperand().isPlatformIndependentConstant() and + getRightOperand().isPlatformIndependentConstant() + } + + override string toString() { result = "..." + getOperator() + "..." } +} + +/** + * A binary arithmetic expression, that is, `+`, `-`, `*`, `/` or `%`. + */ +class ArithmeticBinaryExpr extends @arithmeticbinaryexpr, ArithmeticExpr, BinaryExpr { } + +/** + * A binary logical expression, that is, `&&` or `||`. + */ +class LogicalBinaryExpr extends @logicalbinaryexpr, LogicalExpr, BinaryExpr { } + +/** + * A binary bitwise expression, that is, `<<`, `>>`, `|`, `^`, `&` or `&^`. + */ +class BitwiseBinaryExpr extends @bitwisebinaryexpr, BitwiseExpr, BinaryExpr { } + +/** + * A shift expression, that is, `<<` or `>>`. + */ +class ShiftExpr extends @shiftexpr, BitwiseBinaryExpr { } + +/** + * A comparison expression, that is, `==`, `!=`, `<`, `<=`, `>=` or `>`. + */ +class Comparison extends @comparison, BinaryExpr { } + +/** + * An equality test, that is, `==` or `!=`. + */ +class EqualityTestExpr extends @equalitytest, Comparison { + /** Gets the polarity of this equality test, that is, `true` for `==` and `false` for `!=`. */ + boolean getPolarity() { none() } +} + +/** + * A relational comparison, that is, `<`, `<=`, `>=` or `>`. + */ +class RelationalComparisonExpr extends @relationalcomparison, Comparison { + /** Holds if this comparison is strict, that is, it implies inequality. */ + predicate isStrict() { none() } + + /** + * Gets the greater operand of this comparison, that is, the right operand for + * a `<` or `<=` comparison, and the left operand for `>=` or `>`. + */ + Expr getGreaterOperand() { none() } + + /** + * Gets the lesser operand of this comparison, that is, the left operand for + * a `<` or `<=` comparison, and the right operand for `>=` or `>`. + */ + Expr getLesserOperand() { none() } +} + +/** + * A logical-or expression using `||`. + */ +class LorExpr extends @lorexpr, LogicalBinaryExpr { + override string getOperator() { result = "||" } +} + +class LogOrExpr = LorExpr; + +/** + * A logical-and expression using `&&`. + */ +class LandExpr extends @landexpr, LogicalBinaryExpr { + override string getOperator() { result = "&&" } +} + +class LogAndExpr = LandExpr; + +/** + * An equality test using `==`. + */ +class EqlExpr extends @eqlexpr, EqualityTestExpr { + override string getOperator() { result = "==" } + + override boolean getPolarity() { result = true } +} + +class EqExpr = EqlExpr; + +/** + * An inequality test using `!=`. + */ +class NeqExpr extends @neqexpr, EqualityTestExpr { + override string getOperator() { result = "!=" } + + override boolean getPolarity() { result = false } +} + +/** + * A less-than test using `<`. + */ +class LssExpr extends @lssexpr, RelationalComparisonExpr { + override string getOperator() { result = "<" } + + override predicate isStrict() { any() } + + override Expr getLesserOperand() { result = getLeftOperand() } + + override Expr getGreaterOperand() { result = getRightOperand() } +} + +class LTExpr = LssExpr; + +/** + * A less-than-or-equal test using `<=`. + */ +class LeqExpr extends @leqexpr, RelationalComparisonExpr { + override string getOperator() { result = "<=" } + + override Expr getLesserOperand() { result = getLeftOperand() } + + override Expr getGreaterOperand() { result = getRightOperand() } +} + +class LEExpr = LeqExpr; + +/** + * A greater-than test using `>`. + */ +class GtrExpr extends @gtrexpr, RelationalComparisonExpr { + override string getOperator() { result = ">" } + + override predicate isStrict() { any() } + + override Expr getLesserOperand() { result = getRightOperand() } + + override Expr getGreaterOperand() { result = getLeftOperand() } +} + +class GTExpr = GtrExpr; + +/** + * A greater-than-or-equal test using `>=`. + */ +class GeqExpr extends @geqexpr, RelationalComparisonExpr { + override string getOperator() { result = ">=" } + + override Expr getLesserOperand() { result = getRightOperand() } + + override Expr getGreaterOperand() { result = getLeftOperand() } +} + +class GEExpr = GeqExpr; + +/** + * An addition expression using `+`. + */ +class AddExpr extends @addexpr, ArithmeticBinaryExpr { + override string getOperator() { result = "+" } +} + +/** + * A subtraction expression using `-`. + */ +class SubExpr extends @subexpr, ArithmeticBinaryExpr { + override string getOperator() { result = "-" } +} + +/** + * A bitwise or expression using `|`. + */ +class OrExpr extends @orexpr, BitwiseBinaryExpr { + override string getOperator() { result = "|" } +} + +class BitOrExpr = OrExpr; + +/** + * An exclusive-or expression using `^`. + */ +class XorExpr extends @xorexpr, BitwiseBinaryExpr { + override string getOperator() { result = "^" } +} + +/** + * A multiplication expression using `*`. + */ +class MulExpr extends @mulexpr, ArithmeticBinaryExpr { + override string getOperator() { result = "*" } +} + +/** + * A divison or quotient expression using `/`. + */ +class QuoExpr extends @quoexpr, ArithmeticBinaryExpr { + override predicate mayHaveOwnSideEffects() { any() } + + override string getOperator() { result = "/" } +} + +class DivExpr = QuoExpr; + +/** + * A remainder or modulo expression using `%`. + */ +class RemExpr extends @remexpr, ArithmeticBinaryExpr { + override string getOperator() { result = "%" } +} + +class ModExpr = RemExpr; + +/** + * A left-shift expression using `<<`. + */ +class ShlExpr extends @shlexpr, ShiftExpr { + override string getOperator() { result = "<<" } +} + +class LShiftExpr = ShlExpr; + +/** + * A right-shift expression using `>>`. + */ +class ShrExpr extends @shrexpr, ShiftExpr { + override string getOperator() { result = ">>" } +} + +class RShiftExpr = ShrExpr; + +/** + * A bitwise and-expression using `&`. + */ +class AndExpr extends @andexpr, BitwiseBinaryExpr { + override string getOperator() { result = "&" } +} + +class BitAndExpr = AndExpr; + +/** + * A bitwise and-not expression using `&^`. + */ +class AndNotExpr extends @andnotexpr, BitwiseBinaryExpr { + override string getOperator() { result = "&^" } +} + +/** + * An expression representing a channel type. + */ +class ChanTypeExpr extends @chantypeexpr, Expr { + /** + * Gets the expression representing the type of values flowing through the channel. + */ + Expr getValueTypeExpr() { result = getChildExpr(0) } + + /** Holds if this channel can send data. */ + predicate canSend() { none() } + + /** Holds if this channel can receive data. */ + predicate canReceive() { none() } + + override string toString() { result = "channel type" } +} + +/** + * An expression representing a send-only channel type. + */ +class SendChanTypeExpr extends @sendchantypeexpr, ChanTypeExpr { + override predicate canSend() { any() } +} + +/** + * An expression representing a receive-only channel type. + */ +class RecvChanTypeExpr extends @recvchantypeexpr, ChanTypeExpr { + override predicate canReceive() { any() } +} + +/** + * An expression representing a duplex channel type that can both send and receive data. + */ +class SendRecvChanTypeExpr extends @sendrcvchantypeexpr, ChanTypeExpr { + override predicate canSend() { any() } + + override predicate canReceive() { any() } +} + +/** + * A (possibly qualified) name referring to a package, type, constant, variable, function or label. + */ +class Name extends Expr { + Entity target; + + Name() { this.(Ident).refersTo(target) or this.(SelectorExpr).refersTo(target) } + + /** Gets the entity this name refers to. */ + Entity getTarget() { result = target } +} + +/** A simple (that is, unqualified) name. */ +class SimpleName extends Name, Ident { } + +/** A qualified name. */ +class QualifiedName extends Name, SelectorExpr { } + +/** A name referring to an imported package. */ +class PackageName extends Name { + override PackageEntity target; + + /** Gets the package this name refers to. */ + override PackageEntity getTarget() { result = target } +} + +/** A name referring to a type. */ +class TypeName extends Name { + override TypeEntity target; + + /** Gets the type this name refers to. */ + override TypeEntity getTarget() { result = target } +} + +/** A name referring to a value, that is, a constant, variable or function. */ +class ValueName extends Name { + override ValueEntity target; + + /** Gets the constant, variable or function this name refers to. */ + override ValueEntity getTarget() { result = target } +} + +/** A name referring to a constant. */ +class ConstantName extends ValueName { + override Constant target; + + /** Gets the constant this name refers to. */ + override Constant getTarget() { result = target } + + override predicate isPlatformIndependentConstant() { + target = Builtin::bool(_) + or + target = Builtin::iota() + or + target = Builtin::nil() + or + exists(DeclaredConstant c | c = target | + not c.getSpec().getFile().hasBuildConstraints() and + c.getInit().isPlatformIndependentConstant() + ) + } +} + +/** A name referring to a variable. */ +class VariableName extends ValueName { + override Variable target; + + /** Gets the variable this name refers to. */ + override Variable getTarget() { result = target } +} + +/** A name referring to a function. */ +class FunctionName extends ValueName { + override Function target; + + /** Gets the function this name refers to. */ + override Function getTarget() { result = target } +} + +/** A name referring to a statement label. */ +class LabelName extends Name { + override Label target; + + /** Gets the label this name refers to. */ + override Label getTarget() { result = target } +} + +/** + * Holds if `e` is a type expression, as determined by a bottom-up syntactic + * analysis starting with `TypeName`s. + * + * On a snapshot with full type information, this predicate covers all type + * expressions. However, if type information is missing then not all type names + * may be identified as such, so not all type expressions can be determined by + * a bottom-up analysis. In such cases, `isTypeExprTopDown` below is useful. + */ +private predicate isTypeExprBottomUp(Expr e) { + e instanceof TypeName or + e instanceof ArrayTypeExpr or + e instanceof StructTypeExpr or + e instanceof FuncTypeExpr or + e instanceof InterfaceTypeExpr or + e instanceof MapTypeExpr or + e instanceof ChanTypeExpr or + isTypeExprBottomUp(e.(ParenExpr).getExpression()) or + isTypeExprBottomUp(e.(StarExpr).getBase()) or + isTypeExprBottomUp(e.(Ellipsis).getOperand()) +} + +/** + * Holds if `e` must be a type expression because it either occurs in a syntactic + * position where a type is expected, or it is part of a larger type expression. + * + * This predicate is only needed on snapshots for which type information is + * incomplete. It is an underapproximation; in cases where it is syntactically ambiguous + * whether an expression refers to a type or a value, we conservatively assume that + * it may be the latter and so this predicate does not consider the expression to be + * a type expression. + */ +private predicate isTypeExprTopDown(Expr e) { + e = any(CompositeLit cl).getTypeExpr() + or + e = any(TypeAssertExpr ta).getTypeExpr() + or + e = any(ArrayTypeExpr ae).getElement() + or + e = any(FieldDecl f).getTypeExpr() + or + e = any(ParameterDecl pd).getTypeExpr() + or + e = any(ReceiverDecl rd).getTypeExpr() + or + e = any(ResultVariableDecl rvd).getTypeExpr() + or + e = any(MethodSpec md).getTypeExpr() + or + e = any(MapTypeExpr mt).getKeyTypeExpr() + or + e = any(MapTypeExpr mt).getValueTypeExpr() + or + e = any(ChanTypeExpr ct).getValueTypeExpr() + or + e = any(ValueSpec s).getTypeExpr() + or + e = any(TypeSpec s).getTypeExpr() + or + e = any(TypeSwitchStmt s).getACase().getExpr(_) and + // special case: `nil` is allowed in a type case but isn't a type + not e = Builtin::nil().getAReference() + or + e = any(SelectorExpr sel | isTypeExprTopDown(sel)).getBase() + or + e = any(ParenExpr pe | isTypeExprTopDown(pe)).getExpression() + or + e = any(StarExpr se | isTypeExprTopDown(se)).getBase() + or + e = any(Ellipsis ell | isTypeExprTopDown(ell)).getOperand() +} + +/** An expression referring to a type. */ +class TypeExpr extends Expr { + TypeExpr() { + isTypeExprBottomUp(this) or + isTypeExprTopDown(this) + } +} + +/** An expression referring to a memory location. */ +class ReferenceExpr extends Expr { + ReferenceExpr() { + (this instanceof Ident or this instanceof SelectorExpr) and + not (this instanceof PackageName or this instanceof TypeName or this instanceof LabelName) and + not this instanceof TypeExpr and + not this = any(ImportSpec is).getNameExpr() and + not this = any(File f).getPackageNameExpr() and + not this = any(LabeledStmt ls).getLabelExpr() and + not this = any(BranchStmt bs).getLabelExpr() and + not this = any(FieldDecl f).getNameExpr(_) and + not this = any(ParameterDecl pd).getNameExpr(_) and + not this = any(ReceiverDecl rd).getNameExpr() and + not this = any(ResultVariableDecl rvd).getNameExpr(_) and + not this = any(MethodSpec md).getNameExpr() and + not this = any(StructLit sl).getKey(_) + or + this.(ParenExpr).getExpression() instanceof ReferenceExpr + or + this.(StarExpr).getBase() instanceof ReferenceExpr + or + this instanceof DerefExpr + or + this instanceof IndexExpr + } + + /** Holds if this reference expression occurs in a position where it is being assigned to. */ + predicate isLvalue() { + this = any(Assignment assgn).getLhs(_) + or + this = any(IncDecStmt ids).getExpr() + or + exists(RangeStmt rs | + this = rs.getKey() or + this = rs.getValue() + ) + or + exists(ValueSpec spec, int i | this = spec.getNameExpr(i)) + or + exists(FuncDecl fd | this = fd.getNameExpr()) + } + + /** Holds if this reference expression occurs in a position where it is evaluated to a value. */ + predicate isRvalue() { + not this.isLvalue() + or + this = any(CompoundAssignStmt cmp).getLhs(_) + or + this = any(IncDecStmt ids).getExpr() + } +} + +/** An expression that refers to a value (as opposed to a package, a type or a statement label). */ +class ValueExpr extends Expr { + ValueExpr() { + this.(ReferenceExpr).isRvalue() or + this instanceof BasicLit or + this instanceof FuncLit or + this instanceof CompositeLit or + this.(ParenExpr).getExpression() instanceof ValueExpr or + this instanceof SliceExpr or + this instanceof TypeAssertExpr or + this instanceof CallOrConversionExpr or + this.(StarExpr).getBase() instanceof ValueExpr or + this instanceof OperatorExpr + } +} diff --git a/ql/src/semmle/go/Files.qll b/ql/src/semmle/go/Files.qll new file mode 100644 index 00000000..963c9943 --- /dev/null +++ b/ql/src/semmle/go/Files.qll @@ -0,0 +1,209 @@ +/** Provides classes for working with files and folders. */ + +import go + +/** A file or folder. */ +abstract class Container extends @container { + /** + * Gets the absolute, canonical path of this container, using forward slashes + * as path separator. + * + * The path starts with a _root prefix_ followed by zero or more _path + * segments_ separated by forward slashes. + * + * The root prefix is of one of the following forms: + * + * 1. A single forward slash `/` (Unix-style) + * 2. An upper-case drive letter followed by a colon and a forward slash, + * such as `C:/` (Windows-style) + * 3. Two forward slashes, a computer name, and then another forward slash, + * such as `//FileServer/` (UNC-style) + * + * Path segments are never empty (that is, absolute paths never contain two + * contiguous slashes, except as part of a UNC-style root prefix). Also, path + * segments never contain forward slashes, and no path segment is of the + * form `.` (one dot) or `..` (two dots). + * + * Note that an absolute path never ends with a forward slash, except if it is + * a bare root prefix, that is, the path has no path segments. A container + * whose absolute path has no segments is always a `Folder`, not a `File`. + */ + abstract string getAbsolutePath(); + + /** + * Gets a URL representing the location of this container. + * + * For more information see https://lgtm.com/help/ql/locations#providing-urls. + */ + abstract string getURL(); + + /** + * Gets the relative path of this file or folder from the root folder of the + * analyzed source location. The relative path of the root folder itself is + * the empty string. + * + * This has no result if the container is outside the source root, that is, + * if the root folder is not a reflexive, transitive parent of this container. + */ + string getRelativePath() { + exists(string absPath, string pref | + absPath = getAbsolutePath() and sourceLocationPrefix(pref) + | + absPath = pref and result = "" + or + absPath = pref.regexpReplaceAll("/$", "") + "/" + result and + not result.matches("/%") + ) + } + + /** + * Gets the base name of this container including extension, that is, the last + * segment of its absolute path, or the empty string if it has no segments. + * + * Here are some examples of absolute paths and the corresponding base names + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + * + *
    Absolute pathBase name
    "/tmp/tst.go""tst.go"
    "C:/Program Files (x86)""Program Files (x86)"
    "/"""
    "C:/"""
    "D:/"""
    "//FileServer/"""
    + */ + string getBaseName() { + result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1) + } + + /** + * Gets the extension of this container, that is, the suffix of its base name + * after the last dot character, if any. + * + * In particular, + * + * - if the name does not include a dot, there is no extension, so this + * predicate has no result; + * - if the name ends in a dot, the extension is the empty string; + * - if the name contains multiple dots, the extension follows the last dot. + * + * Here are some examples of absolute paths and the corresponding extensions + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + *
    Absolute pathExtension
    "/tmp/tst.go""go"
    "/tmp/.classpath""classpath"
    "/bin/bash"not defined
    "/tmp/tst2."""
    "/tmp/x.tar.gz""gz"
    + */ + string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) } + + /** + * Gets the stem of this container, that is, the prefix of its base name up to + * (but not including) the last dot character if there is one, or the entire + * base name if there is not. + * + * Here are some examples of absolute paths and the corresponding stems + * (surrounded with quotes to avoid ambiguity): + * + * + * + * + * + * + * + * + *
    Absolute pathStem
    "/tmp/tst.go""tst"
    "/tmp/.classpath"""
    "/bin/bash""bash"
    "/tmp/tst2.""tst2"
    "/tmp/x.tar.gz""x.tar"
    + */ + string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) } + + /** Gets the parent container of this file or folder, if any. */ + Container getParentContainer() { containerparent(result, this) } + + /** Gets a file or sub-folder in this container. */ + Container getAChildContainer() { this = result.getParentContainer() } + + /** Gets a file in this container. */ + File getAFile() { result = getAChildContainer() } + + /** Gets the file in this container that has the given `baseName`, if any. */ + File getFile(string baseName) { + result = getAFile() and + result.getBaseName() = baseName + } + + /** Gets a sub-folder in this container. */ + Folder getAFolder() { result = getAChildContainer() } + + /** Gets the sub-folder in this container that has the given `baseName`, if any. */ + Folder getFolder(string baseName) { + result = getAFolder() and + result.getBaseName() = baseName + } + + /** + * Gets a textual representation of the path of this container. + * + * This is the absolute path of the container. + */ + string toString() { result = getAbsolutePath() } +} + +/** A folder. */ +class Folder extends Container, @folder { + override string getAbsolutePath() { folders(this, result, _) } + + /** Gets the file or subfolder in this folder that has the given `name`, if any. */ + Container getChildContainer(string name) { + result = getAChildContainer() and + result.getBaseName() = name + } + + /** Gets the file in this folder that has the given `stem` and `extension`, if any. */ + File getFile(string stem, string extension) { + result = getAChildContainer() and + result.getStem() = stem and + result.getExtension() = extension + } + + /** Gets a subfolder contained in this folder. */ + Folder getASubFolder() { result = getAChildContainer() } + + /** Gets the URL of this folder. */ + override string getURL() { result = "folder://" + getAbsolutePath() } +} + +/** A file. */ +class File extends Container, @file, Documentable, ExprParent, DeclParent, ScopeNode { + override Location getLocation() { has_location(this, result) } + + override string getAbsolutePath() { files(this, result, _, _, _) } + + /** Gets the number of lines in this file. */ + int getNumberOfLines() { numlines(this, result, _, _) } + + /** Gets the number of lines containing code in this file. */ + int getNumberOfLinesOfCode() { numlines(this, _, result, _) } + + /** Gets the number of lines containing comments in this file. */ + int getNumberOfLinesOfComments() { numlines(this, _, _, result) } + + /** Gets the package name as specified in the package clause of this file. */ + Ident getPackageNameExpr() { result = getChildExpr(0) } + + /** Gets the name of the package to which this file belongs. */ + string getPackageName() { result = getPackageNameExpr().getName() } + + /** Holds if this file contains at least one build constraint. */ + pragma[noinline] + predicate hasBuildConstraints() { exists(BuildConstraintComment bc | this = bc.getFile()) } + + override string toString() { result = Container.super.toString() } + + /** Gets the URL of this file. */ + override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" } +} diff --git a/ql/src/semmle/go/Locations.qll b/ql/src/semmle/go/Locations.qll new file mode 100644 index 00000000..8f9fb08e --- /dev/null +++ b/ql/src/semmle/go/Locations.qll @@ -0,0 +1,81 @@ +/** Provides classes for working with locations and program elements that have locations. */ + +import go + +/** + * A location as given by a file, a start line, a start column, + * an end line, and an end column. + * + * For more information about locations see [LGTM locations](https://lgtm.com/help/ql/locations). + */ +class Location extends @location { + /** Gets the file for this location. */ + File getFile() { locations_default(this, result, _, _, _, _) } + + /** Gets the start line of this location. */ + int getStartLine() { locations_default(this, _, result, _, _, _) } + + /** Gets the start column of this location. */ + int getStartColumn() { locations_default(this, _, _, result, _, _) } + + /** Gets the end line of this location. */ + int getEndLine() { locations_default(this, _, _, _, result, _) } + + /** Gets the end column of this location. */ + int getEndColumn() { locations_default(this, _, _, _, _, result) } + + /** Gets the number of lines covered by this location. */ + int getNumLines() { result = getEndLine() - getStartLine() + 1 } + + /** Gets a textual representation of this element. */ + string toString() { + exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | + hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and + result = filepath + "@" + startline + ":" + startcolumn + "-" + endline + ":" + endcolumn + ) + } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [LGTM locations](https://lgtm.com/help/ql/locations). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + exists(File f | + locations_default(this, f, startline, startcolumn, endline, endcolumn) and + filepath = f.getAbsolutePath() + ) + } +} + +/** A program element with a location. */ +class Locatable extends @locatable { + /** Gets the file this program element comes from. */ + File getFile() { result = getLocation().getFile() } + + /** Gets this element's location. */ + Location getLocation() { has_location(this, result) } + + /** Gets the number of lines covered by this element. */ + int getNumLines() { result = getLocation().getNumLines() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [LGTM locations](https://lgtm.com/help/ql/locations). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets a textual representation of this element. */ + string toString() { result = "locatable element" } +} diff --git a/ql/src/semmle/go/Packages.qll b/ql/src/semmle/go/Packages.qll new file mode 100644 index 00000000..139a4cc5 --- /dev/null +++ b/ql/src/semmle/go/Packages.qll @@ -0,0 +1,22 @@ +/** + * Provides classes for working with packages. + */ + +import go + +/** + * A package. + */ +class Package extends @package { + /** Gets the name of this package. */ + string getName() { packages(this, result, _, _) } + + /** Gets the path of this package. */ + string getPath() { packages(this, _, result, _) } + + /** Gets the scope of this package. */ + PackageScope getScope() { packages(this, _, _, result) } + + /** Gets a textual representation of this element. */ + string toString() { result = "package " + getPath() } +} diff --git a/ql/src/semmle/go/Scopes.qll b/ql/src/semmle/go/Scopes.qll new file mode 100644 index 00000000..4312439f --- /dev/null +++ b/ql/src/semmle/go/Scopes.qll @@ -0,0 +1,581 @@ +/** + * Provides classes for working with scopes and declared objects. + */ + +import go + +/** + * A scope. + */ +class Scope extends @scope { + /** Gets the enclosing scope of this scope, if any. */ + Scope getOuterScope() { scopenesting(this, result) } + + /** Gets a scope nested inside this scope. */ + Scope getAnInnerScope() { this = result.getOuterScope() } + + /** Looks up the entity with the given name in this scope. */ + Entity getEntity(string name) { + result.getName() = name and + result.getScope() = this + } + + /** Gets a textual representation of this scope. */ + string toString() { result = "scope" } +} + +/** Provides helper predicates for working with scopes. */ +module Scope { + /** Gets the universe scope. */ + UniverseScope universe() { any() } +} + +/** + * The universe scope. + */ +class UniverseScope extends @universescope, Scope { + override string toString() { result = "universe scope" } +} + +/** A package scope. */ +class PackageScope extends @packagescope, Scope { + /** Gets the package whose scope this is. */ + Package getPackage() { this = result.getScope() } + + override string toString() { result = "package scope" } +} + +/** A local scope. */ +class LocalScope extends @localscope, Scope, Locatable { + /** Gets the AST node inducing this scope. */ + ScopeNode getNode() { this = result.getScope() } + + /** + * Gets the function scope in which this scope is nested. + * + * For function scopes, this is the scope itself. + */ + FunctionScope getEnclosingFunctionScope() { + result = getOuterScope().(LocalScope).getEnclosingFunctionScope() + } + + override string toString() { result = "local scope" } +} + +/** A local scope induced by a file. */ +class FileScope extends LocalScope { + FileScope() { getNode() instanceof File } +} + +/** A local scope induced by a function definition. */ +class FunctionScope extends LocalScope { + FuncDef f; + + FunctionScope() { getNode() = f.getTypeExpr() } + + /** Gets the function inducing this scope. */ + FuncDef getFunction() { result = f } + + override FunctionScope getEnclosingFunctionScope() { result = this } + + override string toString() { result = "function scope" } +} + +/** + * A declared or built-in entity (that is, package, type, constant, variable, function or label) + */ +class Entity extends @object { + /** + * Gets the name of this entity. + * + * Anonymous entities (such as the receiver variables of interface methods) have the empty string as their name. + */ + string getName() { objects(this, _, result) } + + /** Gets the package in which this entity is declared, if any. */ + Package getPackage() { result.getScope() = this.getScope() } + + /** Holds if this entity is declared in a package with path `pkg` and has the given `name`. */ + predicate hasQualifiedName(string pkg, string name) { + pkg = getPackage().getPath() and + name = getName() + } + + /** Gets the qualified name of this entity, if any. */ + string getQualifiedName() { + exists(string pkg, string name | hasQualifiedName(pkg, name) | result = pkg + "." + name) + } + + /** + * Gets the scope in which this entity is declared, if any. + * + * Entities corresponding to fields and methods do not have a scope. + */ + Scope getScope() { objectscopes(this, result) } + + /** Gets the declaring identifier for this entity. */ + Ident getDeclaration() { result.declares(this) } + + /** Gets an identifier in rvalue position that refers to this entity. */ + Ident getAUse() { result.uses(this) } + + /** Gets a reference to this entity. */ + Name getAReference() { result.getTarget() = this } + + /** Gets the type of this entity. */ + Type getType() { objecttypes(this, result) } + + /** Gets a textual representation of this entity. */ + string toString() { result = getName() } +} + +/** A declared entity (that is, type, constant, variable or function). */ +class DeclaredEntity extends Entity, @declobject { + /** Gets the declaration of this entity. */ + Decl getDecl() { none() } + + /** Gets the expression to which this entity is initialized, if any. */ + Expr getInit() { + exists(ValueSpec spec, int i | + spec.getNameExpr(i) = getDeclaration() and + spec.getInit(i) = result + ) + } +} + +/** A built-in entity (that is, type, constant or function). */ +class BuiltinEntity extends Entity, @builtinobject { } + +/** An imported package. */ +class PackageEntity extends Entity, @pkgobject { } + +/** A built-in or declared named type. */ +class TypeEntity extends Entity, @typeobject { } + +/** A declared named type. */ +class DeclaredType extends TypeEntity, DeclaredEntity, @decltypeobject { + /** Gets the declaration of this type. */ + override TypeDecl getDecl() { result.getASpec() = this.getSpec() } + + /** Gets the declaration specifier declaring this type. */ + TypeSpec getSpec() { result.getNameExpr() = this.getDeclaration() } +} + +/** A built-in named type. */ +class BuiltinType extends TypeEntity, BuiltinEntity, @builtintypeobject { } + +/** A built-in or declared constant, variable, field, method or function. */ +class ValueEntity extends Entity, @valueobject { + /** Gets a data-flow node that reads the value of this entity. */ + Read getARead() { result.reads(this) } + + /** Gets a control-flow node that updates the value of this entity. */ + Write getAWrite() { result.writes(this, _) } +} + +/** A built-in or declared constant. */ +class Constant extends ValueEntity, @constobject { } + +/** A declared constant. */ +class DeclaredConstant extends Constant, DeclaredEntity, @declconstobject { + /** Gets the declaration of this constant. */ + override ConstDecl getDecl() { result.getASpec() = this.getSpec() } + + /** Gets the declaration specifier declaring this constant. */ + ValueSpec getSpec() { result.getANameExpr() = this.getDeclaration() } +} + +/** A built-in constant. */ +class BuiltinConstant extends Constant, BuiltinEntity, @builtinconstobject { } + +/** + * A built-in or declared variable. + * + * Note that Go currently does not have any built-in variables, so this class is effectively + * an alias for `DeclaredVariable`. + */ +class Variable extends ValueEntity, @varobject { } + +/** A declared variable. */ +class DeclaredVariable extends Variable, DeclaredEntity, @declvarobject { + /** Gets the declaration of this variable. */ + override VarDecl getDecl() { result.getASpec() = this.getSpec() } + + /** Gets the declaration specifier declaring this variable. */ + ValueSpec getSpec() { result.getANameExpr() = this.getDeclaration() } +} + +/** A variable declared in a local scope (as opposed to a package scope or the universal scope). */ +class LocalVariable extends DeclaredVariable { + LocalVariable() { getScope() instanceof LocalScope } + + /** Gets the innermost function containing the scope of this variable, if any. */ + FuncDef getDeclaringFunction() { + result = getScope().(LocalScope).getEnclosingFunctionScope().getFunction() + } + + /** Holds if this variable is referenced inside a nested function. */ + predicate isCaptured() { getDeclaringFunction() != getAReference().getEnclosingFunction() } +} + +/** + * A receiver variable or a parameter. + */ +abstract class ParameterOrReceiver extends DeclaredVariable { + FuncDef fn; + + /** Gets the function to which this parameter belongs. */ + FuncDef getFunction() { result = fn } +} + +/** The receiver variable of a method. */ +class ReceiverVariable extends ParameterOrReceiver { + override MethodDecl fn; + + ReceiverVariable() { fn.getReceiverDecl().getNameExpr() = this.getDeclaration() } +} + +/** A (named) function parameter. */ +class Parameter extends ParameterOrReceiver { + Parameter() { fn.getTypeExpr().getAParameterDecl().getNameExpr(_) = this.getDeclaration() } +} + +/** A (named) function result variable. */ +class ResultVariable extends DeclaredVariable { + FuncDef fn; + + ResultVariable() { fn.getTypeExpr().getAResultDecl().getNameExpr(_) = this.getDeclaration() } + + /** Gets the function to which this result variable belongs. */ + FuncDef getFunction() { result = fn } +} + +/** A struct field. */ +class Field extends ValueEntity { + StructType declaringType; + + Field() { fieldstructs(this, declaringType) } + + StructType getDeclaringType() { result = declaringType } +} + +/** A built-in or declared function. */ +class Function extends ValueEntity, @functionobject { + /** Gets an expression representing a call to this function. */ + CallExpr getACallExpr() { result.getCalleeExpr() = getAReference() } + + /** Gets a call to this function. */ + DataFlow::CallNode getACall() { result.getExpr() = getACallExpr() } + + /** Holds if this function has no observable side effects. */ + predicate mayHaveSideEffects() { none() } + + /** + * Holds if calling this function may cause a runtime panic. + * + * This predicate is an over-approximation: it may hold for functions that can never + * cause a runtime panic, but it never fails to hold for functions that can. + */ + predicate mayPanic() { any() } + + /** + * Holds if calling this function always causes a runtime panic. + * + * This predicate is an over-approximation: it may not hold for functions that do + * cause a runtime panic, but it never holds for functions that do not. + */ + predicate mustPanic() { none() } + + /** Gets the number of parameters of this function. */ + int getNumParameter() { result = getType().(SignatureType).getNumParameter() } + + /** Gets the type of the `i`th parameter of this function. */ + Type getParameterType(int i) { result = getType().(SignatureType).getParameterType(i) } + + /** Gets the number of results of this function. */ + int getNumResult() { result = getType().(SignatureType).getNumResult() } + + /** Gets the type of the `i`th result of this function. */ + Type getResultType(int i) { result = getType().(SignatureType).getResultType(i) } + + /** Gets the body of this function, if any. */ + BlockStmt getBody() { none() } +} + +/** A method, that is, a function with a receiver variable. */ +class Method extends Function { + Variable receiver; + + Method() { methodreceivers(this, receiver) } + + override Package getPackage() { + // a method doesn't have a scope, so manually associate it with its receiver's + // package. + result = this.getReceiverType().getPackage() + } + + /** Gets the receiver variable of this method. */ + Variable getReceiver() { result = receiver } + + /** Gets the type of the receiver variable of this method. */ + Type getReceiverType() { result = receiver.getType() } + + /** + * Gets the receiver base type of this method, that is, either the base type of the receiver type + * if it is a pointer type, or the receiver type itself if it is not a pointer type. + */ + Type getReceiverBaseType() { + exists(Type recv | recv = getReceiverType() | + if recv instanceof PointerType + then result = recv.(PointerType).getBaseType() + else result = recv + ) + } + + /** + * Holds if this method has name `m` and its receiver base type has qualified name `tp`. + */ + override predicate hasQualifiedName(string tp, string m) { + tp = getReceiverBaseType().getQualifiedName() and + m = getName() + } + + /** + * Holds if this method has name `m` and its receiver base type is declared in package `pkg` and + * has name `tp`. + */ + predicate hasQualifiedName(string pkg, string tp, string m) { + getReceiverBaseType().hasQualifiedName(pkg, tp) and + m = getName() + } + + /** + * Holds if this method implements the method `m`, that is, if `m` is a method + * on an interface, and this is a method with the same name on a type that + * implements that interface. + */ + predicate implements(Method m) { + exists(Type t | + this = t.getMethod(m.getName()) and + t.implements(m.getReceiverType().getUnderlyingType()) + ) + } + + /** + * Holds if this method implements the method that has qualified name `pkg.tp.name`, that is, if + * `pkg.tp.name` is a method on an interface, and this is a method with the same name on a type + * that implements that interface. + */ + predicate implements(string pkg, string tp, string name) { + exists(Method m | m.hasQualifiedName(pkg, tp, name) | this.implements(m)) + } +} + +/** A declared function. */ +class DeclaredFunction extends Function, DeclaredEntity, @declfunctionobject { + /** Gets the declaration of this function. */ + override FuncDecl getDecl() { result.getNameExpr() = this.getDeclaration() } + + override BlockStmt getBody() { result = getDecl().getBody() } + + override predicate mayHaveSideEffects() { + not exists(getBody()) + or + exists(BlockStmt body | body = getBody() | + body.mayHaveSideEffects() + or + // functions declared in files with build constraints may be defined differently + // for different platforms, so whitelist them to avoid false positives + body.getFile().hasBuildConstraints() + ) + } +} + +/** A built-in function. */ +class BuiltinFunction extends Function, BuiltinEntity, @builtinfunctionobject { + override predicate mayHaveSideEffects() { builtinFunction(getName(), false, _, _) } + + override predicate mayPanic() { builtinFunction(getName(), _, true, _) } + + override predicate mustPanic() { builtinFunction(getName(), _, _, true) } + + /** + * Holds if this function is pure, that is, it has no observable side effects and + * no non-determinism. + */ + predicate isPure() { not mayHaveSideEffects() } +} + +/** A statement label. */ +class Label extends Entity, @labelobject { } + +/** + * Holds if `name` is a built-in function, where + * + * - `isPure` is true if the function has no observable side effects, and false otherwise; + * - `mayPanic` is true if calling this function may cause a panic, and false otherwise; + * - `mustPanic` is ture if calling this function always causes a panic, and false otherwise. + * + * Allocating memory is not considered an observable side effect. + */ +private predicate builtinFunction(string name, boolean isPure, boolean mayPanic, boolean mustPanic) { + name = "append" and isPure = false and mayPanic = false and mustPanic = false + or + name = "cap" and isPure = true and mayPanic = false and mustPanic = false + or + name = "close" and isPure = false and mayPanic = true and mustPanic = false + or + name = "complex" and isPure = true and mayPanic = true and mustPanic = false + or + name = "copy" and isPure = false and mayPanic = true and mustPanic = false + or + name = "delete" and isPure = false and mayPanic = false and mustPanic = false + or + name = "imag" and isPure = true and mayPanic = false and mustPanic = false + or + name = "len" and isPure = true and mayPanic = false and mustPanic = false + or + name = "make" and isPure = true and mayPanic = true and mustPanic = false + or + name = "new" and isPure = true and mayPanic = false and mustPanic = false + or + name = "panic" and isPure = false and mayPanic = true and mustPanic = true + or + name = "print" and isPure = false and mayPanic = false and mustPanic = false + or + name = "println" and isPure = false and mayPanic = false and mustPanic = false + or + name = "real" and isPure = true and mayPanic = false and mustPanic = false + or + name = "recover" and isPure = false and mayPanic = false and mustPanic = false +} + +/** Provides helper predicates for working with built-in objects from the universe scope. */ +module Builtin { + // built-in types + /** Gets the built-in type `bool`. */ + BuiltinType bool() { result.getName() = "bool" } + + /** Gets the built-in type `byte`. */ + BuiltinType byte() { result.getName() = "byte" } + + /** Gets the built-in type `complex64`. */ + BuiltinType complex64() { result.getName() = "complex64" } + + /** Gets the built-in type `complex128`. */ + BuiltinType complex128() { result.getName() = "complex128" } + + /** Gets the built-in type `error`. */ + BuiltinType error() { result.getName() = "error" } + + /** Gets the built-in type `float32`. */ + BuiltinType float32() { result.getName() = "float32" } + + /** Gets the built-in type `float64`. */ + BuiltinType float64() { result.getName() = "float64" } + + /** Gets the built-in type `int`. */ + BuiltinType int_() { result.getName() = "int" } + + /** Gets the built-in type `int8`. */ + BuiltinType int8() { result.getName() = "int8" } + + /** Gets the built-in type `int16`. */ + BuiltinType int16() { result.getName() = "int16" } + + /** Gets the built-in type `int32`. */ + BuiltinType int32() { result.getName() = "int32" } + + /** Gets the built-in type `int64`. */ + BuiltinType int64() { result.getName() = "int64" } + + /** Gets the built-in type `rune`. */ + BuiltinType rune() { result.getName() = "rune" } + + /** Gets the built-in type `string`. */ + BuiltinType string_() { result.getName() = "string" } + + /** Gets the built-in type `uint`. */ + BuiltinType uint() { result.getName() = "uint" } + + /** Gets the built-in type `uint8`. */ + BuiltinType uint8() { result.getName() = "uint8" } + + /** Gets the built-in type `uint16`. */ + BuiltinType uint16() { result.getName() = "uint16" } + + /** Gets the built-in type `uint32`. */ + BuiltinType uint32() { result.getName() = "uint32" } + + /** Gets the built-in type `uint64`. */ + BuiltinType uint64() { result.getName() = "uint64" } + + /** Gets the built-in type `uintptr`. */ + BuiltinType uintptr() { result.getName() = "uintptr" } + + // built-in constants + /** Gets the built-in constant `true`. */ + BuiltinConstant true_() { result.getName() = "true" } + + /** Gets the built-in constant `false`. */ + BuiltinConstant false_() { result.getName() = "false" } + + /** Gets the built-in constant corresponding to `b`. */ + BuiltinConstant bool(boolean b) { + b = true and result = true_() + or + b = false and result = false_() + } + + /** Gets the built-in constant `iota`. */ + BuiltinConstant iota() { result.getName() = "iota" } + + // built-in zero value + /** Gets the built-in zero-value `nil`. */ + BuiltinConstant nil() { result.getName() = "nil" } + + /** Gets the built-in function `append`. */ + BuiltinFunction append() { result.getName() = "append" } + + /** Gets the built-in function `cap`. */ + BuiltinFunction cap() { result.getName() = "cap" } + + /** Gets the built-in function `close`. */ + BuiltinFunction close() { result.getName() = "close" } + + /** Gets the built-in function `complex`. */ + BuiltinFunction complex() { result.getName() = "complex" } + + /** Gets the built-in function `copy`. */ + BuiltinFunction copy() { result.getName() = "copy" } + + /** Gets the built-in function `delete`. */ + BuiltinFunction delete() { result.getName() = "delete" } + + /** Gets the built-in function `imag`. */ + BuiltinFunction imag() { result.getName() = "imag" } + + /** Gets the built-in function `len`. */ + BuiltinFunction len() { result.getName() = "len" } + + /** Gets the built-in function `make`. */ + BuiltinFunction make() { result.getName() = "make" } + + /** Gets the built-in function `new`. */ + BuiltinFunction new() { result.getName() = "new" } + + /** Gets the built-in function `panic`. */ + BuiltinFunction panic() { result.getName() = "panic" } + + /** Gets the built-in function `print`. */ + BuiltinFunction print() { result.getName() = "print" } + + /** Gets the built-in function `println`. */ + BuiltinFunction println() { result.getName() = "println" } + + /** Gets the built-in function `real`. */ + BuiltinFunction real() { result.getName() = "real" } + + /** Gets the built-in function `recover`. */ + BuiltinFunction recover() { result.getName() = "recover" } +} diff --git a/ql/src/semmle/go/Stmt.qll b/ql/src/semmle/go/Stmt.qll new file mode 100644 index 00000000..a0d84869 --- /dev/null +++ b/ql/src/semmle/go/Stmt.qll @@ -0,0 +1,628 @@ +/** + * Provides classes for working with statements. + */ + +import go + +/** + * A statement. + */ +class Stmt extends @stmt, ExprParent, StmtParent { + /** + * Gets the kind of this statement, which is an integer value representing the statement's + * node type. + * + * Note that the mapping from node types to integer kinds is considered an implementation detail + * and subject to change without notice. + */ + int getKind() { stmts(this, result, _, _) } + + /** + * Holds if the execution of this statement may produce observable side effects. + * + * Memory allocation is not considered an observable side effect. + */ + predicate mayHaveSideEffects() { none() } + + /** Gets the first control-flow node in this statement. */ + ControlFlow::Node getFirstControlFlowNode() { result.isFirstNodeOf(this) } +} + +/** + * A bad statement, that is, a statement that could not be parsed. + */ +class BadStmt extends @badstmt, Stmt { + override string toString() { result = "bad statement" } +} + +/** + * A declaration statement. + */ +class DeclStmt extends @declstmt, Stmt, DeclParent { + /** Gets the declaration in this statement. */ + Decl getDecl() { result = getDecl(0) } + + override predicate mayHaveSideEffects() { getDecl().mayHaveSideEffects() } + + override string toString() { result = "declaration statement" } +} + +/** + * An empty statement. + */ +class EmptyStmt extends @emptystmt, Stmt { + override string toString() { result = "empty statement" } +} + +/** + * A labeled statement. + */ +class LabeledStmt extends @labeledstmt, Stmt { + /** Gets the identifier representing the label. */ + Ident getLabelExpr() { result = getChildExpr(0) } + + /** Gets the label. */ + string getLabel() { result = getLabelExpr().getName() } + + /** Gets the statement that is being labeled. */ + Stmt getStmt() { result = getChildStmt(1) } + + override predicate mayHaveSideEffects() { getStmt().mayHaveSideEffects() } + + override string toString() { result = "labeled statement" } +} + +/** + * An expression statement. + */ +class ExprStmt extends @exprstmt, Stmt { + /** Gets the expression. */ + Expr getExpr() { result = getChildExpr(0) } + + override predicate mayHaveSideEffects() { getExpr().mayHaveSideEffects() } + + override string toString() { result = "expression statement" } +} + +/** + * A send statement. + */ +class SendStmt extends @sendstmt, Stmt { + /** Gets the expression representing the channel. */ + Expr getChannel() { result = getChildExpr(0) } + + /** Gets the expression representing the value being sent. */ + Expr getValue() { result = getChildExpr(1) } + + override predicate mayHaveSideEffects() { any() } + + override string toString() { result = "send statement" } +} + +/** + * An increment or decrement statement. + */ +class IncDecStmt extends @incdecstmt, Stmt { + /** Gets the expression. */ + Expr getExpr() { result = getChildExpr(0) } + + /** Gets the increment or decrement operator. */ + string getOperator() { none() } + + override predicate mayHaveSideEffects() { any() } +} + +/** + * An increment statement. + */ +class IncStmt extends @incstmt, IncDecStmt { + override string getOperator() { result = "++" } + + override string toString() { result = "increment statement" } +} + +/** + * A decrement statement. + */ +class DecStmt extends @decstmt, IncDecStmt { + override string getOperator() { result = "--" } + + override string toString() { result = "decrement statement" } +} + +/** + * A (simple or compound) assignment statement. + */ +class Assignment extends @assignment, Stmt { + /** Gets the `i`th left-hand side of this assignment (0-based). */ + Expr getLhs(int i) { + i >= 0 and + result = getChildExpr(-(i + 1)) + } + + /** Gets a left-hand side of this assignment. */ + Expr getAnLhs() { result = getLhs(_) } + + /** Gets the number of left-hand sides of this assignment. */ + int getNumLhs() { result = count(getAnLhs()) } + + /** Gets the unique left-hand side of this assignment, if there is only one. */ + Expr getLhs() { getNumLhs() = 1 and result = getLhs(0) } + + /** Gets the `i`th right-hand side of this assignment (0-based). */ + Expr getRhs(int i) { + i >= 0 and + result = getChildExpr(i + 1) + } + + /** Gets a right-hand side of this assignment. */ + Expr getAnRhs() { result = getRhs(_) } + + /** Gets the number of right-hand sides of this assignment. */ + int getNumRhs() { result = count(getAnRhs()) } + + /** Gets the unique right-hand side of this assignment, if there is only one. */ + Expr getRhs() { getNumRhs() = 1 and result = getRhs(0) } + + /** Holds if this assignment assigns `rhs` to `lhs`. */ + predicate assigns(Expr lhs, Expr rhs) { exists(int i | lhs = getLhs(i) and rhs = getRhs(i)) } + + /** Gets the assignment operator in this statement. */ + string getOperator() { none() } + + override predicate mayHaveSideEffects() { any() } + + override string toString() { result = "... " + getOperator() + " ..." } +} + +/** + * A simple assignment statement, that is, an assignment without a compound operator. + */ +class SimpleAssignStmt extends @simpleassignstmt, Assignment { } + +/** + * A plain assignment statement. + */ +class AssignStmt extends @assignstmt, SimpleAssignStmt { + override string getOperator() { result = "=" } +} + +/** + * A define statement. + */ +class DefineStmt extends @definestmt, SimpleAssignStmt { + override string getOperator() { result = ":=" } +} + +/** + * A compound assignment statement. + */ +class CompoundAssignStmt extends @compoundassignstmt, Assignment { } + +/** + * An add-assign statement using `+=`. + */ +class AddAssignStmt extends @addassignstmt, CompoundAssignStmt { + override string getOperator() { result = "+=" } +} + +/** + * A subtract-assign statement using `-=`. + */ +class SubAssignStmt extends @subassignstmt, CompoundAssignStmt { + override string getOperator() { result = "-=" } +} + +/** + * A multiply-assign statement using `*=`. + */ +class MulAssignStmt extends @mulassignstmt, CompoundAssignStmt { + override string getOperator() { result = "*=" } +} + +/** + * A divide-assign statement using `/=`. + */ +class QuoAssignStmt extends @quoassignstmt, CompoundAssignStmt { + override string getOperator() { result = "/=" } +} + +class DivAssignStmt = QuoAssignStmt; + +/** + * A modulo-assign statement using `%=`. + */ +class RemAssignStmt extends @remassignstmt, CompoundAssignStmt { + override string getOperator() { result = "%=" } +} + +class ModAssignStmt = RemAssignStmt; + +/** + * An and-assign statement using `&=`. + */ +class AndAssignStmt extends @andassignstmt, CompoundAssignStmt { + override string getOperator() { result = "&=" } +} + +/** + * An or-assign statement using `|=`. + */ +class OrAssignStmt extends @orassignstmt, CompoundAssignStmt { + override string getOperator() { result = "|=" } +} + +/** + * An xor-assign statement using `^=`. + */ +class XorAssignStmt extends @xorassignstmt, CompoundAssignStmt { + override string getOperator() { result = "^=" } +} + +/** + * A left-shift-assign statement using `<<=`. + */ +class ShlAssignStmt extends @shlassignstmt, CompoundAssignStmt { + override string getOperator() { result = "<<=" } +} + +class LShiftAssignStmt = ShlAssignStmt; + +/** + * A right-shift-assign statement using `>>=`. + */ +class ShrAssignStmt extends @shrassignstmt, CompoundAssignStmt { + override string getOperator() { result = ">>=" } +} + +class RShiftAssignStmt = ShrAssignStmt; + +/** + * An and-not-assign statement using `&^=`. + */ +class AndNotAssignStmt extends @andnotassignstmt, CompoundAssignStmt { + override string getOperator() { result = "&^=" } +} + +/** + * A `go` statement. + */ +class GoStmt extends @gostmt, Stmt { + /** Gets the call. */ + CallExpr getCall() { result = getChildExpr(0) } + + override predicate mayHaveSideEffects() { getCall().mayHaveSideEffects() } + + override string toString() { result = "go statement" } +} + +/** + * A `defer` statement. + */ +class DeferStmt extends @deferstmt, Stmt { + /** Gets the call being deferred. */ + CallExpr getCall() { result = getChildExpr(0) } + + override predicate mayHaveSideEffects() { getCall().mayHaveSideEffects() } + + override string toString() { result = "defer statement" } +} + +/** + * A `return` statement. + */ +class ReturnStmt extends @returnstmt, Stmt { + /** Gets the `i`th returned expression (0-based) */ + Expr getExpr(int i) { result = getChildExpr(i) } + + /** Gets a returned expression. */ + Expr getAnExpr() { result = getExpr(_) } + + /** Gets the number of returned expressions. */ + int getNumExpr() { result = count(getAnExpr()) } + + /** Gets the unique returned expression, if there is only one. */ + Expr getExpr() { getNumChild() = 1 and result = getExpr(0) } + + override predicate mayHaveSideEffects() { getExpr().mayHaveSideEffects() } + + override string toString() { result = "return statement" } +} + +/** + * A branch statement, for example a `break` or `goto`. + */ +class BranchStmt extends @branchstmt, Stmt { + /** Gets the expression denoting the target label of the branch, if any. */ + Ident getLabelExpr() { result = getChildExpr(0) } + + /** Gets the target label of the branch, if any. */ + string getLabel() { result = getLabelExpr().getName() } +} + +/** A `break` statement. */ +class BreakStmt extends @breakstmt, BranchStmt { + override string toString() { result = "break statement" } +} + +/** A `continue` statement. */ +class ContinueStmt extends @continuestmt, BranchStmt { + override string toString() { result = "continue statement" } +} + +/** A `goto` statement. */ +class GotoStmt extends @gotostmt, BranchStmt { + override string toString() { result = "goto statement" } +} + +/** A `fallthrough` statement. */ +class FallthroughStmt extends @fallthroughstmt, BranchStmt { + override string toString() { result = "fallthrough statement" } +} + +/** A block statement. */ +class BlockStmt extends @blockstmt, Stmt, ScopeNode { + /** Gets the `i`th statement in this block (0-based). */ + Stmt getStmt(int i) { result = getChildStmt(i) } + + /** Gets a statement in this block. */ + Stmt getAStmt() { result = getAChildStmt() } + + /** Gets the number of statements in this block. */ + int getNumStmt() { result = getNumChildStmt() } + + override predicate mayHaveSideEffects() { getAStmt().mayHaveSideEffects() } + + override string toString() { result = "block statement" } +} + +/** An `if` statement. */ +class IfStmt extends @ifstmt, Stmt, ScopeNode { + /** Gets the init statement of this `if` statement, if any. */ + Stmt getInit() { result = getChildStmt(0) } + + /** Gets the condition of this `if` statement. */ + Expr getCond() { result = getChildExpr(1) } + + /** Gets the "then" branch of this `if` statement. */ + BlockStmt getThen() { result = getChildStmt(2) } + + /** Gets the "else" branch of this `if` statement, if any. */ + Stmt getElse() { result = getChildStmt(3) } + + override predicate mayHaveSideEffects() { + getInit().mayHaveSideEffects() or + getCond().mayHaveSideEffects() or + getThen().mayHaveSideEffects() or + getElse().mayHaveSideEffects() + } + + override string toString() { result = "if statement" } +} + +/** A `case` or `default` clause in a `switch` statement. */ +class CaseClause extends @caseclause, Stmt, ScopeNode { + /** Gets the `i`th expression of this `case` clause (0-based). */ + Expr getExpr(int i) { result = getChildExpr(-(i + 1)) } + + /** Gets an expression of this `case` clause. */ + Expr getAnExpr() { result = getAChildExpr() } + + /** Gets the number of expressions of this `case` clause. */ + int getNumExpr() { result = getNumChildExpr() } + + /** Gets the `i`th statement of this `case` clause (0-based). */ + Stmt getStmt(int i) { result = getChildStmt(i) } + + /** Gets a statement of this `case` clause. */ + Stmt getAStmt() { result = getAChildStmt() } + + /** Gets the number of statements of this `case` clause. */ + int getNumStmt() { result = getNumChildStmt() } + + override predicate mayHaveSideEffects() { + getAnExpr().mayHaveSideEffects() or + getAStmt().mayHaveSideEffects() + } + + override string toString() { result = "case clause" } +} + +/** + * A `switch` statement, that is, either an expression switch or a type switch. + */ +class SwitchStmt extends @switchstmt, Stmt, ScopeNode { + /** Gets the init statement of this `switch` statement, if any. */ + Stmt getInit() { result = getChildStmt(0) } + + /** Gets the body of this `switch` statement. */ + BlockStmt getBody() { result = getChildStmt(2) } + + /** Gets the `i`th case clause of this `switch` statement (0-based). */ + CaseClause getCase(int i) { result = getBody().getStmt(i) } + + /** Gets a case clause of this `switch` statement. */ + CaseClause getACase() { result = getCase(_) } + + /** Gets the number of case clauses in this `switch` statement. */ + int getNumCase() { result = count(getACase()) } + + /** Gets the `i`th non-default case clause of this `switch` statement (0-based). */ + CaseClause getNonDefaultCase(int i) { + result = rank[i + 1](CaseClause cc, int j | + cc = getCase(j) and exists(cc.getExpr(_)) + | + cc order by j + ) + } + + /** Gets a non-default case clause of this `switch` statement. */ + CaseClause getANonDefaultCase() { result = getNonDefaultCase(_) } + + /** Gets the number of non-default case clauses in this `switch` statement. */ + int getNumNonDefaultCase() { result = count(getANonDefaultCase()) } + + /** Gets the default case clause of this `switch` statement, if any. */ + CaseClause getDefault() { result = getACase() and not exists(result.getExpr(_)) } +} + +/** + * An expression-switch statement. + */ +class ExpressionSwitchStmt extends @exprswitchstmt, SwitchStmt { + /** Gets the switch expression of this `switch` statement. */ + Expr getExpr() { result = getChildExpr(1) } + + override predicate mayHaveSideEffects() { + getInit().mayHaveSideEffects() or + getBody().mayHaveSideEffects() + } + + override string toString() { result = "expression-switch statement" } +} + +/** + * A type-switch statement. + */ +class TypeSwitchStmt extends @typeswitchstmt, SwitchStmt { + /** Gets the assign statement of this type-switch statement. */ + SimpleAssignStmt getAssign() { result = getChildStmt(1) } + + /** Gets the expression whose type is examined by this `switch` statement. */ + Expr getExpr() { result = getAssign().getRhs() or result = getChildStmt(1).(ExprStmt).getExpr() } + + override predicate mayHaveSideEffects() { any() } + + override string toString() { result = "type-switch statement" } +} + +/** + * A comm clause, that is, a `case` or `default` clause in a `select` statement. + */ +class CommClause extends @commclause, Stmt, ScopeNode { + /** Gets the comm statement of this clause, if any. */ + Stmt getComm() { result = getChildStmt(0) } + + /** Gets the `i`th statement of this clause (0-based). */ + Stmt getStmt(int i) { i >= 0 and result = getChildStmt(i + 1) } + + /** Gets a statement of this clause. */ + Stmt getAStmt() { result = getStmt(_) } + + /** Gets the number of statements of this clause. */ + int getNumStmt() { result = count(getAStmt()) } + + override predicate mayHaveSideEffects() { getAStmt().mayHaveSideEffects() } + + override string toString() { result = "comm clause" } +} + +/** + * A receive statement in a comm clause. + */ +class RecvStmt extends Stmt { + RecvStmt() { this = any(CommClause cc).getComm() and not this instanceof SendStmt } + + /** Gets the `i`th left-hand-side expression of this receive statement, if any. */ + Expr getLhs(int i) { result = this.(Assignment).getLhs(i) } + + /** Gets the number of left-hand-side expressions of this receive statement. */ + int getNumLhs() { result = count(getLhs(_)) } + + /** Gets the receive expression of this receive statement. */ + ArrowExpr getExpr() { + result = this.(ExprStmt).getExpr() or + result = this.(Assignment).getRhs() + } +} + +/** + * A `select` statement. + */ +class SelectStmt extends @selectstmt, Stmt { + /** Gets the body of this `select` statement. */ + BlockStmt getBody() { result = getChildStmt(0) } + + /** + * Gets the `i`th comm clause (that is, `case` or `default` clause) in this `select` statement. + */ + CommClause getCommClause(int i) { result = getBody().getStmt(i) } + + /** + * Gets a comm clause in this `select` statement. + */ + CommClause getACommClause() { result = getCommClause(_) } + + /** Gets the `i`th `case` clause in this `select` statement. */ + CommClause getNonDefaultCommClause(int i) { + result = rank[i + 1](CommClause cc, int j | + cc = getCommClause(j) and exists(cc.getComm()) + | + cc order by j + ) + } + + int getNumNonDefaultCommClause() { result = count(getNonDefaultCommClause(_)) } + + CommClause getDefaultCommClause() { + result = getCommClause(_) and + not exists(result.getComm()) + } + + override predicate mayHaveSideEffects() { any() } + + override string toString() { result = "select statement" } +} + +/** + * A loop, that is, either a `for` statement or a `range` statement. + */ +class LoopStmt extends @loopstmt, Stmt, ScopeNode { + /** Gets the body of this loop. */ + BlockStmt getBody() { none() } +} + +/** + * A `for` statement. + */ +class ForStmt extends @forstmt, LoopStmt { + /** Gets the init statement of this `for` statement, if any. */ + Stmt getInit() { result = getChildStmt(0) } + + /** Gets the condition of this `for` statement. */ + Expr getCond() { result = getChildExpr(1) } + + /** Gets the post statement of this `for` statement. */ + Stmt getPost() { result = getChildStmt(2) } + + override BlockStmt getBody() { result = getChildStmt(3) } + + override predicate mayHaveSideEffects() { + getInit().mayHaveSideEffects() or + getCond().mayHaveSideEffects() or + getPost().mayHaveSideEffects() or + getBody().mayHaveSideEffects() + } + + override string toString() { result = "for statement" } +} + +/** + * A `range` statement. + */ +class RangeStmt extends @rangestmt, LoopStmt { + /** Gets the expression denoting the key of this `range` statement. */ + Expr getKey() { result = getChildExpr(0) } + + /** Get the expression denoting the value of this `range` statement. */ + Expr getValue() { result = getChildExpr(1) } + + /** Gets the domain of this `range` statement. */ + Expr getDomain() { result = getChildExpr(2) } + + override BlockStmt getBody() { result = getChildStmt(3) } + + override predicate mayHaveSideEffects() { any() } + + override string toString() { result = "range statement" } +} diff --git a/ql/src/semmle/go/StringConcatenation.qll b/ql/src/semmle/go/StringConcatenation.qll new file mode 100644 index 00000000..e7020e9d --- /dev/null +++ b/ql/src/semmle/go/StringConcatenation.qll @@ -0,0 +1,59 @@ +/** + * Provides predicates for analyzing string concatenations and their operands. + */ + +import go + +module StringConcatenation { + /** Gets the `n`th operand to the string concatenation defining `node`. */ + DataFlow::Node getOperand(DataFlow::Node node, int n) { + node.getType() instanceof StringType and + exists(DataFlow::BinaryOperationNode add | add = node and add.getOperator() = "+" | + n = 0 and result = add.getLeftOperand() + or + n = 1 and result = add.getRightOperand() + ) + } + + /** Gets an operand to the string concatenation defining `node`. */ + DataFlow::Node getAnOperand(DataFlow::Node node) { result = getOperand(node, _) } + + /** Gets the number of operands to the given concatenation. */ + int getNumOperand(DataFlow::Node node) { result = strictcount(getAnOperand(node)) } + + /** Gets the first operand to the string concatenation defining `node`. */ + DataFlow::Node getFirstOperand(DataFlow::Node node) { result = getOperand(node, 0) } + + /** Gets the last operand to the string concatenation defining `node`. */ + DataFlow::Node getLastOperand(DataFlow::Node node) { + result = getOperand(node, getNumOperand(node) - 1) + } + + /** + * Holds if `src` flows to `dst` through the `n`th operand of the given concatenation operator. + */ + predicate taintStep(DataFlow::Node src, DataFlow::Node dst, DataFlow::Node operator, int n) { + src = getOperand(dst, n) and + operator = dst + } + + /** + * Holds if there is a taint step from `src` to `dst` through string concatenation. + */ + predicate taintStep(DataFlow::Node src, DataFlow::Node dst) { taintStep(src, dst, _, _) } + + /** + * Holds if `node` is the root of a concatenation tree, that is, + * it is a concatenation operator that is not itself the immediate operand to + * another concatenation operator. + */ + predicate isRoot(DataFlow::Node node) { + exists(getAnOperand(node)) and + not node = getAnOperand(_) + } + + /** + * Gets the root of the concatenation tree in which `node` is an operand or operator. + */ + DataFlow::Node getRoot(DataFlow::Node node) { isRoot(result) and node = getAnOperand*(result) } +} diff --git a/ql/src/semmle/go/Types.qll b/ql/src/semmle/go/Types.qll new file mode 100644 index 00000000..0df15336 --- /dev/null +++ b/ql/src/semmle/go/Types.qll @@ -0,0 +1,592 @@ +/** + * Provides classes for working with Go types. + */ + +import go + +/** A Go type. */ +class Type extends @type { + /** Gets the name of this type, if it has one. */ + string getName() { typename(this, result) } + + /** + * Gets the underlying type of this type after any type aliases have been replaced + * with their definition. + */ + Type getUnderlyingType() { result = this } + + /** + * Gets the entity associated with this type. + */ + TypeEntity getEntity() { type_objects(this, result) } + + /** Gets the package in which this type is declared, if any. */ + Package getPackage() { result = this.getEntity().getPackage() } + + /** + * Gets the qualified name of this type, if any. + */ + string getQualifiedName() { result = getEntity().getQualifiedName() } + + /** + * Holds if this type is declared in a package with path `pkg` and has name `name`. + */ + predicate hasQualifiedName(string pkg, string name) { getEntity().hasQualifiedName(pkg, name) } + + /** + * Holds if the method set of this type contains a method named `m` of type `t`. + */ + predicate hasMethod(string m, SignatureType t) { t = getMethod(m).getType() } + + /** + * Gets the method `m` belonging to the method set of this type, if any. + */ + Method getMethod(string m) { + result.getReceiverType() = this and + result.getName() = m + } + + /** + * Gets the field `f` of this type. + * + * This includes fields promoted from an embedded field. + */ + Field getField(string f) { result = getUnderlyingType().getField(f) } + + /** + * Holds if this type implements interface `i`, that is, the method set of `i` + * is contained in the method set of this type. + */ + predicate implements(InterfaceType i) { + isEmptyInterface(i) + or + this.hasMethod(getExampleMethodName(i), _) and + forall(string m, SignatureType t | i.hasMethod(m, t) | this.hasMethod(m, t)) + } + + /** + * Holds if this type implements an interface that has the qualified name `pkg.name`, + * that is, the method set of `pkg.name` is contained in the method set of this type. + */ + predicate implements(string pkg, string name) { + exists(Type t | t.hasQualifiedName(pkg, name) | this.implements(t.getUnderlyingType())) + } + + /** + * Gets the pointer type that has this type as its base type. + */ + PointerType getPointerType() { result.getBaseType() = this } + + /** + * Gets a pretty-printed representation of this type, including its structure where applicable. + */ + string pp() { result = toString() } + + /** + * Gets a basic textual representation of this type. + */ + string toString() { result = getName() } +} + +/** An invalid type. */ +class InvalidType extends @invalidtype, Type { + override string toString() { result = "invalid type" } +} + +/** A basic type. */ +class BasicType extends @basictype, Type { } + +/** Either the normal or literal boolean type */ +class BoolType extends @booltype, BasicType { } + +/** The `bool` type of a non-literal expression */ +class BoolExprType extends @boolexprtype, BoolType { + override string getName() { result = "bool" } +} + +/** A numeric type such as `int` or `float64`. */ +class NumericType extends @numerictype, BasicType { + /** + * Gets the implementation-independent size (in bits) of this numeric type. + * + * This predicate is not defined for types with an implementation-specific size, that is, + * `uint`, `int` or `uintptr`. + */ + int getSize() { none() } + + /** + * Gets a possible implementation-specific size (in bits) of this numeric type. + * + * This predicate is not defined for `uintptr` since the language specification says nothing + * about its size. + */ + int getASize() { result = getSize() } +} + +/** An integer type such as `int` or `uint64`. */ +class IntegerType extends @integertype, NumericType { } + +/** A signed integer type such as `int`. */ +class SignedIntegerType extends @signedintegertype, IntegerType { } + +/** The type `int`. */ +class IntType extends @inttype, SignedIntegerType { + override int getASize() { result = 32 or result = 64 } + + override string getName() { result = "int" } +} + +/** The type `int8`. */ +class Int8Type extends @int8type, SignedIntegerType { + override int getSize() { result = 8 } + + override string getName() { result = "int8" } +} + +/** The type `int16`. */ +class Int16Type extends @int16type, SignedIntegerType { + override int getSize() { result = 16 } + + override string getName() { result = "int16" } +} + +/** The type `int32`. */ +class Int32Type extends @int32type, SignedIntegerType { + override int getSize() { result = 32 } + + override string getName() { result = "int32" } +} + +/** The type `int64`. */ +class Int64Type extends @int64type, SignedIntegerType { + override int getSize() { result = 64 } + + override string getName() { result = "int64" } +} + +/** An unsigned integer type such as `uint`. */ +class UnsignedIntegerType extends @unsignedintegertype, IntegerType { } + +/** The type `uint`. */ +class UintType extends @uinttype, UnsignedIntegerType { + override int getASize() { result = 32 or result = 64 } + + override string getName() { result = "uint" } +} + +/** The type `uint8`. */ +class Uint8Type extends @uint8type, UnsignedIntegerType { + override int getSize() { result = 8 } + + override string getName() { result = "uint8" } +} + +/** The type `uint16`. */ +class Uint16Type extends @uint16type, UnsignedIntegerType { + override int getSize() { result = 16 } + + override string getName() { result = "uint16" } +} + +/** The type `uint32`. */ +class Uint32Type extends @uint32type, UnsignedIntegerType { + override int getSize() { result = 32 } + + override string getName() { result = "uint32" } +} + +/** The type `uint64`. */ +class Uint64Type extends @uint64type, UnsignedIntegerType { + override int getSize() { result = 64 } + + override string getName() { result = "uint64" } +} + +/** The type `uintptr`. */ +class UintptrType extends @uintptrtype, BasicType { + override string getName() { result = "uintptr" } +} + +/** A floating-point type such as `float64`. */ +class FloatType extends @floattype, NumericType { } + +/** The type `float32`. */ +class Float32Type extends @float32type, FloatType { + override int getSize() { result = 32 } + + override string getName() { result = "float32" } +} + +/** The type `float64`. */ +class Float64Type extends @float64type, FloatType { + override int getSize() { result = 64 } + + override string getName() { result = "float64" } +} + +/** A complex-number type such as `complex64`. */ +class ComplexType extends @complextype, NumericType { } + +/** The type `complex64`. */ +class Complex64Type extends @complex64type, ComplexType { + override int getSize() { result = 64 } + + override string getName() { result = "complex64" } +} + +/** The type `complex128`. */ +class Complex128Type extends @complex128type, ComplexType { + override int getSize() { result = 128 } + + override string getName() { result = "complex128" } +} + +/** Either the normal or literal string type */ +class StringType extends @stringtype, BasicType { } + +/** The `string` type of a non-literal expression */ +class StringExprType extends @stringexprtype, StringType { + override string getName() { result = "string" } +} + +/** The type `unsafe.Pointer`. */ +class UnsafePointerType extends @unsafepointertype, BasicType { + override string getName() { result = "unsafe.Pointer" } +} + +/** The type of a literal. */ +class LiteralType extends @literaltype, BasicType { } + +/** The type of a bool literal. */ +class BoolLiteralType extends @boolliteraltype, LiteralType, BoolType { + override string toString() { result = "bool literal" } +} + +/** The type of an integer literal. */ +class IntLiteralType extends @intliteraltype, LiteralType, SignedIntegerType { + override string toString() { result = "int literal" } +} + +/** The type of a rune literal. */ +class RuneLiteralType extends @runeliteraltype, LiteralType, SignedIntegerType { + override string toString() { result = "rune literal" } +} + +/** The type of a float literal. */ +class FloatLiteralType extends @floatliteraltype, LiteralType, FloatType { + override string toString() { result = "float literal" } +} + +/** The type of a complex literal. */ +class ComplexLiteralType extends @complexliteraltype, LiteralType, ComplexType { + override string toString() { result = "complex literal" } +} + +/** The type of a string literal. */ +class StringLiteralType extends @stringliteraltype, LiteralType, StringType { + override string toString() { result = "string literal" } +} + +/** The type of `nil`. */ +class NilLiteralType extends @nilliteraltype, LiteralType { + override string toString() { result = "nil literal" } +} + +/** A composite type, that is, not a basic type. */ +class CompositeType extends @compositetype, Type { } + +/** An array type. */ +class ArrayType extends @arraytype, CompositeType { + /** Gets the element type of this array type. */ + Type getElementType() { element_type(this, result) } + + /** Gets the length of this array type as a string. */ + string getLengthString() { array_length(this, result) } + + /** Gets the length of this array type if it can be represented as a QL integer. */ + int getLength() { result = getLengthString().toInt() } + + override Package getPackage() { result = this.getElementType().getPackage() } + + override string pp() { result = "[" + getLength() + "]" + getElementType().pp() } + + override string toString() { result = "array type" } +} + +/** A slice type. */ +class SliceType extends @slicetype, CompositeType { + /** Gets the element type of this slice type. */ + Type getElementType() { element_type(this, result) } + + override Package getPackage() { result = this.getElementType().getPackage() } + + override string pp() { result = "[]" + getElementType().pp() } + + override string toString() { result = "slice type" } +} + +/** A struct type. */ +class StructType extends @structtype, CompositeType { + /** + * Holds if this struct contains a field `name` with type `tp`; + * `isEmbedded` is true if the field is embedded. + * + * Note that this predicate does not take promoted fields into account. + */ + predicate hasOwnField(int i, string name, Type tp, boolean isEmbedded) { + exists(string n | component_types(this, i, n, tp) | + if n = "" + then ( + isEmbedded = true and + ( + name = tp.(NamedType).getName() + or + name = tp.(PointerType).getBaseType().(NamedType).getName() + ) + ) else ( + isEmbedded = false and + name = n + ) + ) + } + + /** + * Get a field with the name `name`; `isEmbedded` is true if the field is embedded. + * + * Note that this does not take promoted fields into account. + */ + Field getOwnField(string name, boolean isEmbedded) { + result.getDeclaringType() = this and + result.getName() = name and + this.hasOwnField(_, name, _, isEmbedded) + } + + private predicate hasEmbeddedField(Type tp, int depth) { + hasFieldOrMethodCand(_, tp, depth, true, false) + or + exists(PointerType embeddedPtr | + hasFieldOrMethodCand(_, embeddedPtr, depth, true, false) and + tp = embeddedPtr.getBaseType() + ) + } + + private predicate hasFieldOrMethodCand( + string name, Type tp, int depth, boolean isEmbedded, boolean isMethod + ) { + hasOwnField(_, name, tp, isEmbedded) and depth = 0 and isMethod = false + or + exists(Type embedded | hasEmbeddedField(embedded, depth - 1) | + embedded.getUnderlyingType().(StructType).hasOwnField(_, name, tp, isEmbedded) and + isMethod = false + or + exists(MethodDecl md | md.getReceiverType() = embedded | + name = md.getName() and + tp = md.getType() + ) and + isEmbedded = false and + isMethod = true + ) + } + + private predicate hasFieldOrMethod(string name, Type tp, boolean isMethod) { + exists(int mindepth | + mindepth = min(int depth | hasFieldOrMethodCand(name, _, depth, _, _)) and + hasFieldOrMethodCand(name, tp, mindepth, _, isMethod) and + (strictcount(getFieldCand(name, mindepth, _)) = 1 or isMethod = true) + ) + } + + /** + * Holds if this struct contains a field `name` with type `tp`, possibly inside a (nested) + * embedded field. + */ + predicate hasField(string name, Type tp) { hasFieldOrMethod(name, tp, false) } + + private Field getFieldCand(string name, int depth, boolean isEmbedded) { + result = this.getOwnField(name, isEmbedded) and depth = 0 + or + exists(Type embedded | hasEmbeddedField(embedded, depth - 1) | + result = embedded.getUnderlyingType().(StructType).getOwnField(name, isEmbedded) + ) + } + + override Field getField(string name) { + exists(int mindepth | + mindepth = min(int depth | exists(getFieldCand(name, depth, _))) and + result = getFieldCand(name, mindepth, _) and + strictcount(getFieldCand(name, mindepth, _)) = 1 + ) + } + + override predicate hasMethod(string name, SignatureType tp) { hasFieldOrMethod(name, tp, true) } + + language[monotonicAggregates] + override string pp() { + result = "struct { " + + concat(int i, string name, Type tp | + component_types(this, i, name, tp) + | + name + " " + tp.pp(), "; " order by i + ) + " }" + } + + override string toString() { result = "struct type" } +} + +/** A pointer type. */ +class PointerType extends @pointertype, CompositeType { + /** Gets the base type of this pointer type. */ + Type getBaseType() { base_type(this, result) } + + override Package getPackage() { result = this.getBaseType().getPackage() } + + override Method getMethod(string m) { + // https://golang.org/ref/spec#Method_sets: "the method set of a pointer type *T is + // the set of all methods declared with receiver *T or T" + result = CompositeType.super.getMethod(m) + or + result = getBaseType().getMethod(m) + } + + override string pp() { result = "* " + getBaseType().pp() } + + override string toString() { result = "pointer type" } +} + +/** An interface type. */ +class InterfaceType extends @interfacetype, CompositeType { + /** Gets the type of method `name` of this interface type. */ + Type getMethodType(string name) { component_types(this, _, name, result) } + + override predicate hasMethod(string m, SignatureType t) { t = getMethodType(m) } + + language[monotonicAggregates] + override string pp() { + result = "interface { " + + concat(string name, Type tp | + tp = getMethodType(name) + | + name + " " + tp.pp(), "; " order by name + ) + " }" + } + + override string toString() { result = "interface type" } +} + +/** A tuple type. */ +class TupleType extends @tupletype, CompositeType { + /** Gets the `i`th component type of this tuple type. */ + Type getComponentType(int i) { component_types(this, i, _, result) } + + language[monotonicAggregates] + override string pp() { + result = "(" + concat(int i, Type tp | tp = getComponentType(i) | tp.pp(), ", " order by i) + + ")" + } + + override string toString() { result = "tuple type" } +} + +/** A signature type. */ +class SignatureType extends @signaturetype, CompositeType { + /** Gets the `i`th parameter type of this signature type. */ + Type getParameterType(int i) { i >= 0 and component_types(this, i + 1, _, result) } + + /** Gets the `i`th result type of this signature type. */ + Type getResultType(int i) { i >= 0 and component_types(this, -(i + 1), _, result) } + + /** Gets the number of parameters specified by this signature. */ + int getNumParameter() { result = count(int i | exists(getParameterType(i))) } + + /** Gets the number of results specified by this signature. */ + int getNumResult() { result = count(int i | exists(getResultType(i))) } + + language[monotonicAggregates] + override string pp() { + result = "func(" + concat(int i, Type tp | tp = getParameterType(i) | tp.pp(), ", " order by i) + + ") " + concat(int i, Type tp | tp = getResultType(i) | tp.pp(), ", " order by i) + } + + override string toString() { result = "signature type" } +} + +/** A map type. */ +class MapType extends @maptype, CompositeType { + /** Gets the key type of this map type. */ + Type getKeyType() { key_type(this, result) } + + /** Gets the value type of this map type. */ + Type getValueType() { element_type(this, result) } + + override string pp() { result = "[" + getKeyType().pp() + "]" + getValueType().pp() } + + override string toString() { result = "map type" } +} + +/** A channel type. */ +class ChanType extends @chantype, CompositeType { + /** Gets the element type of this channel type. */ + Type getElementType() { element_type(this, result) } + + /** Holds if this channel can send data. */ + predicate canSend() { none() } + + /** Holds if this channel can receive data. */ + predicate canReceive() { none() } +} + +/** A channel type that can only send. */ +class SendChanType extends @sendchantype, ChanType { + override predicate canSend() { any() } + + override string pp() { result = "chan<- " + getElementType().pp() } + + override string toString() { result = "send-channel type" } +} + +/** A channel type that can only receive. */ +class RecvChanType extends @recvchantype, ChanType { + override predicate canReceive() { any() } + + override string pp() { result = "<-chan " + getElementType().pp() } + + override string toString() { result = "receive-channel type" } +} + +/** A channel type that can both send and receive. */ +class SendRecvChanType extends @sendrcvchantype, ChanType { + override predicate canSend() { any() } + + override predicate canReceive() { any() } + + override string pp() { result = "chan " + getElementType().pp() } + + override string toString() { result = "send-receive-channel type" } +} + +/** A named type. */ +class NamedType extends @namedtype, CompositeType { + /** Gets a method with name `m` defined on this type. */ + MethodDecl getMethodDecl(string m) { + result.getName() = m and + this = result.getReceiverBaseType() + } + + /** Gets the type which this type is defined to be. */ + Type getBaseType() { underlying_type(this, result) } + + override Type getUnderlyingType() { result = getBaseType().getUnderlyingType() } +} + +/** + * Holds if `i` is the empty interface type, which is implemented by every type with a method set. + */ +private predicate isEmptyInterface(InterfaceType i) { not i.hasMethod(_, _) } + +/** + * Gets the name of a method in the method set of `i`. + * + * This is used to restrict the set of interfaces to consider in the definition of `implements`, + * so it does not matter which method name is chosen (we use the lexicographically least). + */ +private string getExampleMethodName(InterfaceType i) { result = min(string m | i.hasMethod(m, _)) } diff --git a/ql/src/semmle/go/Util.qll b/ql/src/semmle/go/Util.qll new file mode 100644 index 00000000..ff597cdc --- /dev/null +++ b/ql/src/semmle/go/Util.qll @@ -0,0 +1,18 @@ +/** This module provides general utility classes and predicates. */ + +/** + * A Boolean value. + * + * This is a self-binding convenience wrapper for `boolean`. + */ +class Boolean extends boolean { + Boolean() { this = true or this = false } +} + +/** + * Gets a regexp pattern that matches common top-level domain names. + */ +string commonTLD() { + // according to ranking by http://google.com/search?q=site:.<> + result = "(?:com|org|edu|gov|uk|net|io)(?![a-z0-9])" +} diff --git a/ql/src/semmle/go/controlflow/BasicBlocks.qll b/ql/src/semmle/go/controlflow/BasicBlocks.qll new file mode 100644 index 00000000..f15a8161 --- /dev/null +++ b/ql/src/semmle/go/controlflow/BasicBlocks.qll @@ -0,0 +1,199 @@ +/** + * Provides classes for working with basic blocks. + */ + +import go +private import ControlFlowGraphImpl + +/** + * Holds if `nd` starts a new basic block. + */ +private predicate startsBB(ControlFlow::Node nd) { + count(nd.getAPredecessor()) != 1 + or + nd.getAPredecessor().isBranch() +} + +/** + * Holds if the first node of basic block `succ` is a control flow + * successor of the last node of basic block `bb`. + */ +private predicate succBB(BasicBlock bb, BasicBlock succ) { succ = bb.getLastNode().getASuccessor() } + +/** + * Holds if the first node of basic block `bb` is a control flow + * successor of the last node of basic block `pre`. + */ +private predicate predBB(BasicBlock bb, BasicBlock pre) { succBB(pre, bb) } + +/** Holds if `bb` is an entry basic block. */ +private predicate entryBB(BasicBlock bb) { bb.getFirstNode().isEntryNode() } + +/** Holds if `bb` is an exit basic block. */ +private predicate exitBB(BasicBlock bb) { bb.getLastNode().isExitNode() } + +cached +private module Internal { + /** + * Holds if `succ` is a control flow successor of `nd` within the same basic block. + */ + private predicate intraBBSucc(ControlFlow::Node nd, ControlFlow::Node succ) { + succ = nd.getASuccessor() and + not startsBB(succ) + } + + /** + * Holds if `nd` is the `i`th node in basic block `bb`. + * + * In other words, `i` is the shortest distance from a node `bb` + * that starts a basic block to `nd` along the `intraBBSucc` relation. + */ + cached + predicate bbIndex(BasicBlock bb, ControlFlow::Node nd, int i) = + shortestDistances(startsBB/1, intraBBSucc/2)(bb, nd, i) + + cached + int bbLength(BasicBlock bb) { result = strictcount(ControlFlow::Node nd | bbIndex(bb, nd, _)) } + + cached + predicate reachableBB(BasicBlock bb) { + entryBB(bb) + or + exists(BasicBlock predBB | succBB(predBB, bb) | reachableBB(predBB)) + } +} +private import Internal + +/** Holds if `dom` is an immediate dominator of `bb`. */ +cached +private predicate bbIDominates(BasicBlock dom, BasicBlock bb) = + idominance(entryBB/1, succBB/2)(_, dom, bb) + +/** Holds if `dom` is an immediate post-dominator of `bb`. */ +cached +private predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) = + idominance(exitBB/1, predBB/2)(_, dom, bb) + +/** + * A basic block, that is, a maximal straight-line sequence of control flow nodes + * without branches or joins. + * + * At the database level, a basic block is represented by its first control flow node. + */ +class BasicBlock extends TControlFlowNode { + BasicBlock() { startsBB(this) } + + /** Gets a basic block succeeding this one. */ + BasicBlock getASuccessor() { succBB(this, result) } + + /** Gets a basic block preceding this one. */ + BasicBlock getAPredecessor() { result.getASuccessor() = this } + + /** Gets a node in this block. */ + ControlFlow::Node getANode() { result = getNode(_) } + + /** Gets the node at the given position in this block. */ + ControlFlow::Node getNode(int pos) { bbIndex(this, result, pos) } + + /** Gets the first node in this block. */ + ControlFlow::Node getFirstNode() { result = this } + + /** Gets the last node in this block. */ + ControlFlow::Node getLastNode() { result = getNode(length() - 1) } + + /** Gets the length of this block. */ + int length() { result = bbLength(this) } + + /** Gets the basic block that immediately dominates this basic block. */ + ReachableBasicBlock getImmediateDominator() { bbIDominates(result, this) } + + /** Gets the innermost function or file to which this basic block belongs. */ + ControlFlow::Root getRoot() { result = getFirstNode().getRoot() } + + /** Gets a textual representation of this basic block. */ + string toString() { result = "basic block" } + + /** + * Holds if this basic block is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + getFirstNode().hasLocationInfo(filepath, startline, startcolumn, _, _) and + getLastNode().hasLocationInfo(_, _, _, endline, endcolumn) + } +} + +/** + * An entry basic block, that is, a basic block whose first node is an entry node. + */ +class EntryBasicBlock extends BasicBlock { + EntryBasicBlock() { entryBB(this) } +} + +/** + * A basic block that is reachable from an entry basic block. + */ +class ReachableBasicBlock extends BasicBlock { + ReachableBasicBlock() { reachableBB(this) } + + /** + * Holds if this basic block strictly dominates `bb`. + */ + cached + predicate strictlyDominates(ReachableBasicBlock bb) { bbIDominates+(this, bb) } + + /** + * Holds if this basic block dominates `bb`. + * + * This predicate is reflexive: each reachable basic block dominates itself. + */ + predicate dominates(ReachableBasicBlock bb) { + bb = this or + strictlyDominates(bb) + } + + /** + * Holds if this basic block strictly post-dominates `bb`. + */ + cached + predicate strictlyPostDominates(ReachableBasicBlock bb) { bbIPostDominates+(this, bb) } + + /** + * Holds if this basic block post-dominates `bb`. + * + * This predicate is reflexive: each reachable basic block post-dominates itself. + */ + predicate postDominates(ReachableBasicBlock bb) { + bb = this or + strictlyPostDominates(bb) + } +} + +/** + * A reachable basic block with more than one predecessor. + */ +class ReachableJoinBlock extends ReachableBasicBlock { + ReachableJoinBlock() { getFirstNode().isJoin() } + + /** + * Holds if this basic block belongs to the dominance frontier of `b`, that is + * `b` dominates a predecessor of this block, but not this block itself. + * + * Algorithm from Cooper et al., "A Simple, Fast Dominance Algorithm" (Figure 5), + * who in turn attribute it to Ferrante et al., "The program dependence graph and + * its use in optimization". + */ + predicate inDominanceFrontierOf(ReachableBasicBlock b) { + b = getAPredecessor() and not b = getImmediateDominator() + or + exists(ReachableBasicBlock prev | inDominanceFrontierOf(prev) | + b = prev.getImmediateDominator() and + not b = getImmediateDominator() + ) + } +} diff --git a/ql/src/semmle/go/controlflow/ControlFlowGraph.qll b/ql/src/semmle/go/controlflow/ControlFlowGraph.qll new file mode 100644 index 00000000..65f58795 --- /dev/null +++ b/ql/src/semmle/go/controlflow/ControlFlowGraph.qll @@ -0,0 +1,227 @@ +/** + * Provides classes for working with a CFG-based program representation. + */ + +import go +private import ControlFlowGraphImpl + +/** Provides helper predicates for mapping btween CFG nodes and the AST. */ +module ControlFlow { + class Root extends AstNode { + Root() { exists(this.(File).getADecl()) or exists(this.(FuncDef).getBody()) } + + predicate isRootOf(AstNode nd) { + this = nd.getEnclosingFunction() + or + not exists(nd.getEnclosingFunction()) and + this = nd.getFile() + } + + EntryNode getEntryNode() { result = ControlFlow::entryNode(this) } + + ExitNode getExitNode() { result = ControlFlow::exitNode(this) } + } + + /** + * A node in the intra-procedural control-flow graph of a Go function or file. + * + * Nodes correspond to expressions and statements that compute a value or perform + * an operation (as opposed to providing syntactic structure or type information). + * + * There are also synthetic entry and exit nodes for each Go function and file + * that mark the beginning and the end, respectively, of the execution of the + * function and the loading of the file. + */ + class Node extends TControlFlowNode { + /** Gets a node that directly follows this one in the control-flow graph. */ + Node getASuccessor() { result = CFG::succ(this) } + + /** Gets a node that directly precedes this one in the control-flow graph. */ + Node getAPredecessor() { this = result.getASuccessor() } + + /** Holds if this is a node with more than one successor. */ + predicate isBranch() { strictcount(getASuccessor()) > 1 } + + /** Holds if this is a node with more than one predecessor. */ + predicate isJoin() { strictcount(getAPredecessor()) > 1 } + + /** Holds if this is the first control-flow node in `subtree`. */ + predicate isFirstNodeOf(AstNode subtree) { CFG::firstNode(subtree, this) } + + /** Holds if this node is the (unique) entry node of a function or file. */ + predicate isEntryNode() { this instanceof MkEntryNode } + + /** Holds if this node is the (unique) exit node of a function or file. */ + predicate isExitNode() { this instanceof MkExitNode } + + /** Gets the basic block to which this node belongs. */ + BasicBlock getBasicBlock() { result.getANode() = this } + + /** Gets the innermost function or file to which this node belongs. */ + Root getRoot() { none() } + + /** Gets the file to which this node belongs. */ + File getFile() { hasLocationInfo(result.getAbsolutePath(), _, _, _, _) } + + /** + * Gets a textual representation of this control flow node. + */ + string toString() { result = "control-flow node" } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + filepath = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } + } + + /** + * A control-flow node that initializes or updates the value of a constant, a variable, + * a field, or an (array, slice, or map) element. + */ + class WriteNode extends Node { + IR::WriteInstruction self; + + WriteNode() { this = self } + + /** Gets the left-hand side of this write. */ + IR::WriteTarget getLhs() { result = self.getLhs() } + + /** Gets the right-hand side of this write. */ + DataFlow::Node getRhs() { self.getRhs() = result.asInstruction() } + + /** Holds if this node sets variable or constant `v` to `rhs`. */ + predicate writes(ValueEntity v, DataFlow::Node rhs) { self.writes(v, rhs.asInstruction()) } + + /** Holds if this node defines SSA variable `v` to be `rhs`. */ + predicate definesSsaVariable(SsaVariable v, DataFlow::Node rhs) { + self.getLhs().asSsaVariable() = v and + self.getRhs() = rhs.asInstruction() + } + + /** Holds if this node sets the value of field `f` on `base` to `rhs`. */ + predicate writesField(DataFlow::Node base, Field f, DataFlow::Node rhs) { + exists(IR::FieldTarget trg | trg = self.getLhs() | + trg.getBase() = base.asInstruction() and + trg.getField() = f and + self.getRhs() = rhs.asInstruction() + ) + } + + /** Holds if this node sets the value of element `idx` on `base` to `rhs`. */ + predicate writesElement(DataFlow::Node base, DataFlow::Node index, DataFlow::Node rhs) { + exists(IR::ElementTarget trg | trg = self.getLhs() | + trg.getBase() = base.asInstruction() and + trg.getIndex() = index.asInstruction() and + self.getRhs() = rhs.asInstruction() + ) + } + } + + /** + * A control-flow node recording the fact that a certain expression is has a known + * Boolean value at this point in the program. + */ + class ConditionGuardNode extends IR::Instruction, MkConditionGuardNode { + Expr cond; + + boolean outcome; + + ConditionGuardNode() { this = MkConditionGuardNode(cond, outcome) } + + private predicate ensuresAux(Expr expr, boolean b) { + expr = cond and b = outcome + or + expr = any(ParenExpr par | ensuresAux(par, b)).getExpression() + or + expr = any(NotExpr ne | ensuresAux(ne, b.booleanNot())).getOperand() + or + expr = any(LandExpr land | ensuresAux(land, true)).getAnOperand() and + b = true + or + expr = any(LorExpr lor | ensuresAux(lor, false)).getAnOperand() and + b = false + } + + /** Holds if this guard ensures that the result of `nd` is `b`. */ + predicate ensures(DataFlow::Node nd, boolean b) { + ensuresAux(any(Expr e | nd = DataFlow::exprNode(e)), b) + } + + /** Holds if this guard ensures that `lesser <= greater + bias` holds. */ + predicate ensuresLeq(DataFlow::Node lesser, DataFlow::Node greater, int bias) { + exists(DataFlow::RelationalComparisonNode rel, boolean b | + ensures(rel, b) and + rel.leq(b, lesser, greater, bias) + ) + or + ensuresEq(lesser, greater) and + bias = 0 + } + + /** Holds if this guard ensures that `i = j` holds. */ + predicate ensuresEq(DataFlow::Node i, DataFlow::Node j) { + exists(DataFlow::EqualityTestNode eq, boolean b | + ensures(eq, b) and + eq.eq(b, i, j) + ) + } + + /** Holds if this guard ensures that `i != j` holds. */ + predicate ensuresNeq(DataFlow::Node i, DataFlow::Node j) { + exists(DataFlow::EqualityTestNode eq, boolean b | + ensures(eq, b.booleanNot()) and + eq.eq(b, i, j) + ) + } + + /** + * Holds if this guard dominates basic block `bb`, that is, the guard + * is known to hold at `bb`. + */ + predicate dominates(ReachableBasicBlock bb) { + this = bb.getANode() or + dominates(bb.getImmediateDominator()) + } + + /** + * Gets the condition whose outcome the guard concerns. + */ + Expr getCondition() { result = cond } + + override Root getRoot() { result.isRootOf(cond) } + + override string toString() { result = cond + " is " + outcome } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + cond.hasLocationInfo(filepath, _, _, startline, startcolumn) and + endline = startline and + endcolumn = startcolumn + } + } + + /** + * Gets the entry node of function or file `root`. + */ + Node entryNode(Root root) { result = MkEntryNode(root) } + + /** + * Gets the exit node of function or file `root`. + */ + Node exitNode(Root root) { result = MkExitNode(root) } +} + +class Write = ControlFlow::WriteNode; diff --git a/ql/src/semmle/go/controlflow/ControlFlowGraphImpl.qll b/ql/src/semmle/go/controlflow/ControlFlowGraphImpl.qll new file mode 100644 index 00000000..3dc5849a --- /dev/null +++ b/ql/src/semmle/go/controlflow/ControlFlowGraphImpl.qll @@ -0,0 +1,1847 @@ +/** + * INTERNAL: Analyses should use module `ControlFlowGraph` instead. + * + * Provides predicates for building intra-procedural CFGs. + */ + +import go + +class PlainBlock extends BlockStmt { + PlainBlock() { + not this = any(SwitchStmt sw).getBody() and not this = any(SelectStmt sel).getBody() + } +} + +private predicate notBlankIdent(Expr e) { not e instanceof BlankIdent } + +private predicate pureLvalue(ReferenceExpr e) { not e.isRvalue() } + +private predicate isCondRoot(Expr e) { + e = any(LogicalBinaryExpr lbe).getLeftOperand() + or + e = any(ForStmt fs).getCond() + or + e = any(IfStmt is).getCond() +} + +private predicate isCond(Expr e) { + isCondRoot(e) or + e = any(LogicalBinaryExpr lbe | isCond(lbe)).getRightOperand() or + e = any(ParenExpr par | isCond(par)).getExpression() +} + +/** + * A node in the intra-procedural control-flow graph of a Go function or file. + * + * There are two kinds of control-flow nodes: + * + * 1. Instructions: these are nodes that correspond to expressions and statements + * that compute a value or perform an operation (as opposed to providing syntactic + * structure or type information). + * 2. Synthetic nodes: + * - Entry and exit nodes for each Go function and file that mark the beginning and the end, + * respectively, of the execution of the function and the loading of the file; + * - Skip nodes that are semantic no-ops, but make CFG construction easier. + */ +newtype TControlFlowNode = + /** + * A control-flow node that represents the evaluation of an expression. + */ + MkExprNode(Expr e) { CFG::hasSemantics(e) } or + /** + * A control-flow node that represents the initialization of an element of a composite literal. + */ + MkLiteralElementInitNode(Expr e) { e = any(CompositeLit lit).getAnElement() } or + /** + * A control-flow node that represents the implicit index of an element in a slice or array literal. + */ + MkImplicitLiteralElementIndex(Expr e) { + exists(CompositeLit lit | not lit instanceof StructLit | + e = lit.getAnElement() and + not e instanceof KeyValueExpr + ) + } or + /** + * A control-flow node that represents a (single) assignment. + * + * Assignments with multiple left-hand sides are split up into multiple assignment nodes, + * one for each left-hand side. Assignments to `_` are not represented in the control-flow graph. + */ + MkAssignNode(AstNode assgn, int i) { + // the `i`th assignment in a (possibly multi-)assignment + notBlankIdent(assgn.(Assignment).getLhs(i)) + or + // the `i`th name declared in a (possibly multi-)declaration specifier + notBlankIdent(assgn.(ValueSpec).getNameExpr(i)) + or + // the assignment to the "key" variable in a `range` statement + notBlankIdent(assgn.(RangeStmt).getKey()) and i = 0 + or + // the assignment to the "value" variable in a `range` statement + notBlankIdent(assgn.(RangeStmt).getValue()) and i = 1 + } or + /** + * A control-flow node that represents the implicit right-hand side of a compound assignment. + * + * For example, the compound assignment `x += 1` has an implicit right-hand side `x + 1`. + */ + MkCompoundAssignRhsNode(CompoundAssignStmt assgn) or + /** + * A control-flow node that represents the `i`th component of a tuple expression `base`. + */ + MkExtractNode(AstNode s, int i) { + // in an assignment `x, y, z = tuple` + exists(Assignment assgn | + s = assgn and + exists(assgn.getRhs()) and + assgn.getNumLhs() > 1 and + exists(assgn.getLhs(i)) + ) + or + // in a declaration `var x, y, z = tuple` + exists(ValueSpec spec | + s = spec and + exists(spec.getInit()) and + spec.getNumName() > 1 and + exists(spec.getNameExpr(i)) + ) + or + // in a `range` statement + exists(RangeStmt rs | s = rs | + exists(rs.getKey()) and i = 0 + or + exists(rs.getValue()) and i = 1 + ) + or + // in a return statement `return f()` where `f` has multiple return values + exists(ReturnStmt ret, CallExpr call | s = ret | + call = ret.getExpr().stripParens() and + exists(call.getType().(TupleType).getComponentType(i)) + ) + or + // in a call `f(g())` where `g` has multiple return values + exists(CallExpr outer, CallExpr inner | s = outer | + inner = outer.getArgument(0).stripParens() and + outer.getNumArgument() = 1 and + exists(inner.getType().(TupleType).getComponentType(i)) + ) + } or + /** + * A control-flow node that represents the zero value to which a variable without an initializer + * expression is initialized. + */ + MkZeroInitNode(ValueEntity v) { + exists(ValueSpec spec, int i | + not exists(spec.getAnInit()) and + spec.getNameExpr(i) = v.getDeclaration() + ) + or + exists(v.(ResultVariable).getFunction().getBody()) + } or + /** + * A control-flow node that represents a function declaration. + */ + MkFuncDeclNode(FuncDecl fd) or + /** + * A control-flow node that represents a `defer` statement. + */ + MkDeferNode(DeferStmt def) or + /** + * A control-flow node that represents a `go` statement. + */ + MkGoNode(GoStmt go) or + /** + * A control-flow node that represents the fact that `e` is known to evaluate to + * `outcome`. + */ + MkConditionGuardNode(Expr e, Boolean outcome) { isCondRoot(e) } or + /** + * A control-flow node that represents an increment or decrement statement. + */ + MkIncDecNode(IncDecStmt ids) or + /** + * A control-flow node that represents the implicit right-hand side of an increment or decrement statement. + */ + MkIncDecRhs(IncDecStmt ids) or + /** + * A control-flow node that represents the implicit operand 1 of an increment or decrement statement. + */ + MkImplicitOne(IncDecStmt ids) or + /** + * A control-flow node that represents a return from a function. + */ + MkReturnNode(ReturnStmt ret) + or + /** + * A control-flow node that represents the implicit write to a named result variable in a return statement. + */ + MkResultWriteNode(ResultVariable var, int i, ReturnStmt ret) { + ret.getEnclosingFunction().getResultVar(i) = var and + exists(ret.getAnExpr()) + } + or + /** + * A control-flow node that represents the implicit read of a named result variable upon returning from + * a function (after any deferred calls have been executed). + */ + MkResultReadNode(ResultVariable var) or + /** + * A control-flow node that represents a no-op. + * + * These control-flow nodes correspond to Go statements that have no runtime semantics other than potentially + * influencing control flow: the branching statements `continue`, `break`, `fallthrough` and `goto`; empty + * blocks; empty statements; and import and type declarations. + */ + MkSkipNode(AstNode skip) { + skip instanceof BranchStmt + or + skip instanceof EmptyStmt + or + skip.(PlainBlock).getNumStmt() = 0 + or + skip instanceof ImportDecl + or + skip instanceof TypeDecl + or + pureLvalue(skip) + or + skip.(CaseClause).getNumStmt() = 0 + or + skip.(CommClause).getNumStmt() = 0 + } or + /** + * A control-flow node that represents a `select` operation. + */ + MkSelectNode(SelectStmt sel) or + /** + * A control-flow node that represents a `send` operation. + */ + MkSendNode(SendStmt send) or + /** + * A control-flow node that represents the initialization of a parameter to its corresponding argument. + */ + MkParameterInit(ParameterOrReceiver parm) { exists(parm.getFunction().getBody()) } or + /** + * A control-flow node that represents the argument corresponding to a parameter. + */ + MkArgumentNode(ParameterOrReceiver parm) { exists(parm.getFunction().getBody()) } or + /** + * A control-flow node that represents the initialization of a result variable to its zero value. + */ + MkResultInit(ResultVariable rv) { exists(rv.getFunction().getBody()) } or + /** + * A control-flow node that represents the operation of retrieving the next (key, value) pair in a + * `range` statement, if any. + */ + MkNextNode(RangeStmt rs) or + /** + * A control-flow node that represents the implicit `true` expression in `switch { ... }`. + */ + MkImplicitTrue(ExpressionSwitchStmt stmt) { not exists(stmt.getExpr()) } or + /** + * A control-flow node that represents the implicit comparison or type check performed by + * the the `i`th expression of a case clause `cc`. + */ + MkCaseNode(CaseClause cc, int i) { exists(cc.getExpr(i)) } or + /** + * A control-flow node that represents the implicit lower bound of a slice expression. + */ + MkImplicitLowerSliceBound(SliceExpr sl) { not exists(sl.getLow()) } or + /** + * A control-flow node that represents the implicit upper bound of a simple slice expression. + */ + MkImplicitUpperSliceBound(SliceExpr sl) { not exists(sl.getHigh()) } or + /** + * A control-flow node that represents the implicit max bound of a simple slice expression. + */ + MkImplicitMaxSliceBound(SliceExpr sl) { not exists(sl.getMax()) } or + /** + * A control-flow node that represents the start of the execution of a function or file. + */ + MkEntryNode(ControlFlow::Root root) or + /** + * A control-flow node that represents the end of the execution of a function or file. + */ + MkExitNode(ControlFlow::Root root) + +/** A representation of the target of a write. */ +newtype TWriteTarget = + /** A write target that is represented explicitly in the AST. */ + MkLhs(TControlFlowNode write, Expr lhs) { + exists(AstNode assgn, int i | write = MkAssignNode(assgn, i) | + lhs = assgn.(Assignment).getLhs(i).stripParens() + or + lhs = assgn.(ValueSpec).getNameExpr(i) + or + exists(RangeStmt rs | rs = assgn | + i = 0 and lhs = rs.getKey().stripParens() + or + i = 1 and lhs = rs.getValue().stripParens() + ) + ) + or + exists(IncDecStmt ids | write = MkIncDecNode(ids) | lhs = ids.getExpr().stripParens()) + or + exists(ParameterOrReceiver parm | write = MkParameterInit(parm) | lhs = parm.getDeclaration()) + or + exists(ResultVariable res | write = MkResultInit(res) | lhs = res.getDeclaration()) + } + or + /** A write target for an element in a compound literal, viewed as a field write. */ + MkLiteralElementTarget(MkLiteralElementInitNode elt) + or + /** A write target for a returned expression, viewed as a write to the corresponding result variable. */ + MkResultWriteTarget(MkResultWriteNode w) + +class SkipNode extends ControlFlow::Node, MkSkipNode { + AstNode skip; + + SkipNode() { this = MkSkipNode(skip) } + + override ControlFlow::Root getRoot() { result.isRootOf(skip) } + + override string toString() { result = "skip" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + skip.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +class EntryNode extends ControlFlow::Node, MkEntryNode { + ControlFlow::Root root; + + EntryNode() { this = MkEntryNode(root) } + + override ControlFlow::Root getRoot() { result = root } + + override string toString() { result = "entry" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + root.hasLocationInfo(filepath, startline, startcolumn, _, _) and + endline = startline and + endcolumn = startcolumn + } +} + +class ExitNode extends ControlFlow::Node, MkExitNode { + ControlFlow::Root root; + + ExitNode() { this = MkExitNode(root) } + + override ControlFlow::Root getRoot() { result = root } + + override string toString() { result = "exit" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + root.hasLocationInfo(filepath, _, _, endline, endcolumn) and + endline = startline and + endcolumn = startcolumn + } +} + +/** + * Provides classes and predicates for computing the control-flow graph. + */ +cached +module CFG { + /** + * The target of a branch statement, which is either the label of a labeled statement or + * the special target `""` referring to the innermost enclosing loop or `switch`. + */ + private class BranchTarget extends string { + BranchTarget() { this = any(LabeledStmt ls).getLabel() or this = "" } + } + + private module BranchTarget { + /** Holds if this is the target of branch statement `stmt` or the label of compound statement `stmt`. */ + BranchTarget of(Stmt stmt) { + exists(BranchStmt bs | bs = stmt | + result = bs.getLabel() + or + not exists(bs.getLabel()) and result = "" + ) + or + exists(LabeledStmt ls | stmt = ls.getStmt() | result = ls.getLabel()) + or + (stmt instanceof LoopStmt or stmt instanceof SwitchStmt or stmt instanceof SelectStmt) and + result = "" + } + } + + private newtype TCompletion = + /** A completion indicating that an expression or statement was evaluated successfully. */ + Done() or + /** + * A completion indicating that an expression was successfully evaluated to Boolean value `b`. + * + * Note that many Boolean expressions are modelled as having completion `Done()` instead. + * Completion `Bool` is only used in contexts where the Boolean value can be determined. + */ + Bool(boolean b) { b = true or b = false } or + /** + * A completion indicating that execution of a (compound) statement ended with a `break` + * statement targeting the given label. + */ + Break(BranchTarget lbl) or + /** + * A completion indicating that execution of a (compound) statement ended with a `continue` + * statement targeting the given label. + */ + Continue(BranchTarget lbl) or + /** + * A completion indicating that execution of a (compound) statement ended with a `fallthrough` + * statement. + */ + Fallthrough() or + /** + * A completion indicating that execution of a (compound) statement ended with a `return` + * statement. + */ + Return() or + /** + * A completion indicating that execution of a statement or expression may have ended with + * a panic being raised. + */ + Panic() + + private Completion normalCompletion() { result.isNormal() } + + private class Completion extends TCompletion { + predicate isNormal() { this = Done() or this = Bool(_) } + + Boolean getOutcome() { this = Done() or this = Bool(result) } + + string toString() { + this = Done() and result = "normal" + or + exists(boolean b | this = Bool(b) | result = b.toString()) + or + exists(BranchTarget lbl | + this = Break(lbl) and result = "break " + lbl + or + this = Continue(lbl) and result = "continue " + lbl + ) + or + this = Fallthrough() and result = "fallthrough" + or + this = Return() and result = "return" + or + this = Panic() and result = "panic" + } + } + + /** + * Holds if `e` is an expression that has runtime semantics and hence should be represented in the + * control-flow graph. + */ + cached + predicate hasSemantics(Expr e) { + // exclude expressions that do not denote a value + not e instanceof TypeExpr and + not e = any(FieldDecl f).getTag() and + not e instanceof KeyValueExpr and + not e = any(SelectorExpr sel).getSelector() and + not e = any(StructLit sl).getKey(_) and + not (e instanceof Ident and not e instanceof ReferenceExpr) and + not (e instanceof SelectorExpr and not e instanceof ReferenceExpr) and + not pureLvalue(e) and + not isStructural(e) and + // exclude expressions that are not evaluated at runtime + not e = any(ImportSpec is).getPathExpr() and + not e.getParent*() = any(ArrayTypeExpr ate).getLength() and + // sub-expressions of constant expressions are not evaluated (even if they don't look constant + // themselves) + not constRoot(e.getParent+()) + } + + /** + * Holds if `e` is an expression that purely serves grouping or control-flow purposes. + * + * Examples include parenthesized expressions and short-circuiting Boolean expressions used in + * a loop or `if` condition. + */ + private predicate isStructural(Expr e) { + e instanceof LogicalBinaryExpr and + isCond(e) + or + e instanceof ParenExpr + } + + /** + * Gets a constant root, that is, an expression that is constant but whose parent expression is not. + * + * As an exception to the latter, for a grouping expression such as `(c)` where `c` is constant + * we still consider it to be a constant root, even though its parent expression is also constant. + */ + private predicate constRoot(Expr root) { + exists(Expr c | + c.isConst() and + not c.getParent().(Expr).isConst() and + root = stripStructural(c) + ) + } + + /** + * Strips off any structural components from `e`. + */ + private Expr stripStructural(Expr e) { + if isStructural(e) then + result = stripStructural(e.getAChildExpr()) + else + result = e + } + + private class ControlFlowTree extends AstNode { + predicate firstNode(ControlFlow::Node first) { none() } + + predicate lastNode(ControlFlow::Node last, Completion cmpl) { + // propagate abnormal completion from children + lastNode(this.getAChild(), last, cmpl) and + not cmpl.isNormal() + } + + predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + exists(int i | + lastNode(getChildTreeRanked(i), pred, normalCompletion()) and + firstNode(getChildTreeRanked(i + 1), succ) + ) + } + + final ControlFlowTree getChildTreeRanked(int i) { + exists(int j | + result = getChildTree(j) and + j = rank[i + 1](int k | exists(getChildTree(k))) + ) + } + + ControlFlowTree getFirstChildTree() { result = getChildTreeRanked(0) } + + ControlFlowTree getLastChildTree() { + result = max(ControlFlowTree ch, int j | ch = getChildTree(j) | ch order by j) + } + + ControlFlowTree getChildTree(int i) { none() } + } + + private class AtomicTree extends ControlFlowTree { + ControlFlow::Node nd; + + Completion cmpl; + + AtomicTree() { + exists(Expr e | + e = this.(Expr) and + e.isConst() and + nd = mkExprOrSkipNode(this) + | + if e.isPlatformIndependentConstant() and exists(e.getBoolValue()) then + cmpl = Bool(e.getBoolValue()) + else + cmpl = Done() + ) + or + this instanceof Ident and + not this.(Expr).isConst() and + nd = mkExprOrSkipNode(this) and + cmpl = Done() + or + this instanceof BreakStmt and + nd = MkSkipNode(this) and + cmpl = Break(BranchTarget::of(this)) + or + this instanceof ContinueStmt and + nd = MkSkipNode(this) and + cmpl = Continue(BranchTarget::of(this)) + or + this instanceof Decl and + nd = MkSkipNode(this) and + cmpl = Done() + or + this instanceof EmptyStmt and + nd = MkSkipNode(this) and + cmpl = Done() + or + this instanceof FallthroughStmt and + nd = MkSkipNode(this) and + cmpl = Fallthrough() + or + this instanceof FuncLit and + nd = MkExprNode(this) and + cmpl = Done() + or + this instanceof PlainBlock and + nd = MkSkipNode(this) and + cmpl = Done() + or + this instanceof SelectorExpr and + not this.(SelectorExpr).getBase() instanceof ValueExpr and + nd = mkExprOrSkipNode(this) and + cmpl = Done() + } + + override predicate firstNode(ControlFlow::Node first) { first = nd } + + override predicate lastNode(ControlFlow::Node last, Completion c) { last = nd and c = cmpl } + } + + abstract private class PostOrderTree extends ControlFlowTree { + abstract ControlFlow::Node getNode(); + + Completion getCompletion() { result = Done() } + + override predicate firstNode(ControlFlow::Node first) { + firstNode(getFirstChildTree(), first) + or + not exists(getChildTree(_)) and + first = getNode() + } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + super.lastNode(last, cmpl) + or + last = getNode() and cmpl = getCompletion() + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + super.succ(pred, succ) + or + lastNode(getLastChildTree(), pred, normalCompletion()) and + succ = getNode() + } + } + + abstract private class PreOrderTree extends ControlFlowTree { + abstract ControlFlow::Node getNode(); + + override predicate firstNode(ControlFlow::Node first) { first = getNode() } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + super.lastNode(last, cmpl) + or + lastNode(getLastChildTree(), last, cmpl) + or + not exists(getChildTree(_)) and + last = getNode() and + cmpl = Done() + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + super.succ(pred, succ) + or + pred = getNode() and + firstNode(getFirstChildTree(), succ) + } + } + + private class WrapperTree extends ControlFlowTree { + WrapperTree() { + this instanceof ConstDecl or + this instanceof DeclStmt or + this instanceof ExprStmt or + this instanceof KeyValueExpr or + this instanceof LabeledStmt or + this instanceof ParenExpr or + this instanceof PlainBlock or + this instanceof VarDecl + } + + override predicate firstNode(ControlFlow::Node first) { firstNode(getFirstChildTree(), first) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + super.lastNode(last, cmpl) + or + lastNode(getLastChildTree(), last, cmpl) + or + exists(LoopStmt ls | this = ls.getBody() | + lastNode(this, last, Continue(BranchTarget::of(ls))) and + cmpl = Done() + ) + } + + override ControlFlowTree getChildTree(int i) { + i = 0 and result = this.(DeclStmt).getDecl() + or + i = 0 and result = this.(ExprStmt).getExpr() + or + result = this.(GenDecl).getSpec(i) + or + exists(KeyValueExpr kv | kv = this | + not kv.getLiteral() instanceof StructLit and + i = 0 and + result = kv.getKey() + or + i = 1 and result = kv.getValue() + ) + or + i = 0 and result = this.(LabeledStmt).getStmt() + or + i = 0 and result = this.(ParenExpr).getExpression() + or + result = this.(PlainBlock).getStmt(i) + } + } + + private class AssignmentTree extends ControlFlowTree { + AssignmentTree() { + this instanceof Assignment or + this instanceof ValueSpec + } + + Expr getLhs(int i) { + result = this.(Assignment).getLhs(i) or + result = this.(ValueSpec).getNameExpr(i) + } + + int getNumLhs() { + result = this.(Assignment).getNumLhs() or + result = this.(ValueSpec).getNumName() + } + + Expr getRhs(int i) { + result = this.(Assignment).getRhs(i) or + result = this.(ValueSpec).getInit(i) + } + + int getNumRhs() { + result = this.(Assignment).getNumRhs() or + result = this.(ValueSpec).getNumInit() + } + + predicate isExtractingAssign() { getNumRhs() = 1 and getNumLhs() > 1 } + + override predicate firstNode(ControlFlow::Node first) { + not this instanceof RecvStmt and + firstNode(getLhs(0), first) + } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + ControlFlowTree.super.lastNode(last, cmpl) + or + exists(int nl, int nr | nl = getNumLhs() and nr = getNumRhs() | + last = MkAssignNode(this, nl - 1) + or + not exists(MkAssignNode(this, nl - 1)) and + ( + exists(ControlFlow::Node rhs | lastNode(getRhs(nr - 1), rhs, normalCompletion()) | + if nl = nr then last = rhs else last = MkExtractNode(this, nl - 1) + ) + or + not exists(getRhs(nr - 1)) and + lastNode(getLhs(nl - 1), last, normalCompletion()) + ) + ) and + cmpl = Done() + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + ControlFlowTree.super.succ(pred, succ) + or + exists(int i | lastNode(getLhs(i), pred, normalCompletion()) | + firstNode(getLhs(i + 1), succ) + or + not this instanceof RecvStmt and + i = getNumLhs() - 1 and + ( + firstNode(getRhs(0), succ) + or + not exists(getRhs(_)) and + succ = epilogueNodeRanked(1) + ) + ) + or + exists(int i | + lastNode(getRhs(i), pred, normalCompletion()) and + firstNode(getRhs(i + 1), succ) + ) + or + exists(int i | + pred = epilogueNodeRanked(i) and + succ = epilogueNodeRanked(i + 1) + ) + } + + ControlFlow::Node epilogueNodeRanked(int i) { + exists(int j | + result = epilogueNode(j) and + j = rank[i + 1](int k | exists(epilogueNode(k))) + ) + } + + private ControlFlow::Node epilogueNode(int i) { + not this instanceof RecvStmt and + i = -2 and + ( + lastNode(getRhs(getNumRhs() - 1), result, normalCompletion()) + or + not exists(getRhs(_)) and + lastNode(getLhs(getNumLhs() - 1), result, normalCompletion()) + ) + or + i = -1 and + result = MkCompoundAssignRhsNode(this) + or + exists(int j | + result = MkExtractNode(this, j) and + i = 2 * j + or + result = MkZeroInitNode(any(ValueEntity v | getLhs(j) = v.getDeclaration())) and + i = 2 * j + or + result = MkAssignNode(this, j) and + i = 2 * j + 1 + ) + } + } + + private class BinaryExprTree extends PostOrderTree, BinaryExpr { + override ControlFlow::Node getNode() { result = MkExprNode(this) } + + override Completion getCompletion() { + result = PostOrderTree.super.getCompletion() + or + // runtime panic due to division by zero or comparison of incomparable interface values + (this instanceof DivExpr or this instanceof EqualityTestExpr) and + not this.(Expr).isConst() and + result = Panic() + } + + override ControlFlowTree getChildTree(int i) { + i = 0 and result = getLeftOperand() + or + i = 1 and result = getRightOperand() + } + } + + private class LogicalBinaryExprTree extends BinaryExprTree, LogicalBinaryExpr { + boolean shortCircuit; + + LogicalBinaryExprTree() { + this instanceof LandExpr and shortCircuit = false + or + this instanceof LorExpr and shortCircuit = true + } + + private ControlFlow::Node getGuard(boolean outcome) { + result = MkConditionGuardNode(getLeftOperand(), outcome) + } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + lastNode(getAnOperand(), last, cmpl) and + not cmpl.isNormal() + or + if isCond(this) + then ( + last = getGuard(shortCircuit) and + cmpl = Bool(shortCircuit) + or + lastNode(getRightOperand(), last, cmpl) + ) else ( + last = MkExprNode(this) and + cmpl = Done() + ) + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + exists(Completion lcmpl | + lastNode(getLeftOperand(), pred, lcmpl) and + succ = getGuard(lcmpl.getOutcome()) + ) + or + pred = getGuard(shortCircuit.booleanNot()) and + firstNode(getRightOperand(), succ) + or + not isCond(this) and + ( + pred = getGuard(shortCircuit) and + succ = MkExprNode(this) + or + exists(Completion rcmpl | + lastNode(getRightOperand(), pred, rcmpl) and + rcmpl.isNormal() and + succ = MkExprNode(this) + ) + ) + } + } + + private class CallExprTree extends PostOrderTree, CallExpr { + private predicate isSpecial() { + this = any(DeferStmt defer).getCall() or + this = any(GoStmt go).getCall() + } + + override ControlFlow::Node getNode() { + not isSpecial() and + result = MkExprNode(this) + } + + override Completion getCompletion() { + not getTarget().mustPanic() and + result = Done() + or + (not exists(getTarget()) or getTarget().mayPanic()) and + result = Panic() + } + + override ControlFlowTree getChildTree(int i) { + i = 0 and result = getCalleeExpr() + or + result = getArgument(i - 1) and + // calls to `make` and `new` can have type expressions as arguments + not result instanceof TypeExpr + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + // interpose implicit argument destructuring nodes between last argument + // and call itself; this is for cases like `f(g())` where `g` has multiple + // results + exists(ControlFlow::Node mid | PostOrderTree.super.succ(pred, mid) | + if mid = getNode() then + succ = getEpilogueNode(0) + else + succ = mid + ) + or + exists(int i | + pred = getEpilogueNode(i) and + succ = getEpilogueNode(i+1) + ) + } + + private ControlFlow::Node getEpilogueNode(int i) { + result = MkExtractNode(this, i) + or + i = max(int j | exists(MkExtractNode(this, j)))+1 and + result = getNode() + or + not exists(MkExtractNode(this, _)) and + i = 0 and + result = getNode() + } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + PostOrderTree.super.lastNode(last, cmpl) + or + isSpecial() and + lastNode(getLastChildTree(), last, cmpl) + } + } + + private class CaseClauseTree extends ControlFlowTree, CaseClause { + private ControlFlow::Node getExprStart(int i) { + firstNode(getExpr(i), result) + or + getExpr(i) instanceof TypeExpr and + result = MkCaseNode(this, i) + } + + private ControlFlow::Node getBodyStart() { + firstNode(getStmt(0), result) or result = MkSkipNode(this) + } + + override predicate firstNode(ControlFlow::Node first) { + first = getExprStart(0) + or + not exists(getAnExpr()) and + first = getBodyStart() + } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + ControlFlowTree.super.lastNode(last, cmpl) + or + // TODO: shouldn't be here + last = MkCaseNode(this, getNumExpr() - 1) and + cmpl = Bool(false) + or + last = MkSkipNode(this) and + cmpl = Done() + or + lastNode(getStmt(getNumStmt() - 1), last, cmpl) + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + ControlFlowTree.super.succ(pred, succ) + or + exists(int i | + lastNode(getExpr(i), pred, normalCompletion()) and + succ = MkCaseNode(this, i) + or + pred = MkCaseNode(this, i) and + ( + succ = getExprStart(i + 1) + or + succ = getBodyStart() + ) + ) + } + + override ControlFlowTree getChildTree(int i) { result = getStmt(i) } + } + + private class CommClauseTree extends ControlFlowTree, CommClause { + override predicate firstNode(ControlFlow::Node first) { firstNode(getComm(), first) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + ControlFlowTree.super.lastNode(last, cmpl) + or + last = MkSkipNode(this) and + cmpl = Done() + or + lastNode(getStmt(getNumStmt() - 1), last, cmpl) + } + + override ControlFlowTree getChildTree(int i) { result = getStmt(i) } + } + + private class CompositeLiteralTree extends ControlFlowTree, CompositeLit { + private ControlFlow::Node getElementInit(int i) { + result = MkLiteralElementInitNode(getElement(i)) + } + + private ControlFlow::Node getElementStart(int i) { + exists(Expr elt | elt = getElement(i) | + result = MkImplicitLiteralElementIndex(elt) + or + (elt instanceof KeyValueExpr or this instanceof StructLit) and + firstNode(getElement(i), result) + ) + } + + override predicate firstNode(ControlFlow::Node first) { first = MkExprNode(this) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + ControlFlowTree.super.lastNode(last, cmpl) + or + last = getElementInit(getNumElement() - 1) and + cmpl = Done() + or + not exists(getElement(_)) and + last = MkExprNode(this) and + cmpl = Done() + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + firstNode(pred) and + succ = getElementStart(0) + or + exists(int i | + pred = MkImplicitLiteralElementIndex(getElement(i)) and + firstNode(getElement(i), succ) + or + lastNode(getElement(i), pred, normalCompletion()) and + succ = getElementInit(i) + or + pred = getElementInit(i) and + succ = getElementStart(i + 1) + ) + } + } + + private class ConversionExprTree extends PostOrderTree, ConversionExpr { + override ControlFlow::Node getNode() { result = MkExprNode(this) } + + override ControlFlowTree getChildTree(int i) { i = 0 and result = getOperand() } + } + + private class DeferStmtTree extends PostOrderTree, DeferStmt { + override ControlFlow::Node getNode() { result = MkDeferNode(this) } + + override ControlFlowTree getChildTree(int i) { i = 0 and result = getCall() } + } + + private class FuncDeclTree extends PostOrderTree, FuncDecl { + override ControlFlow::Node getNode() { result = MkFuncDeclNode(this) } + + override ControlFlowTree getChildTree(int i) { i = 0 and result = getNameExpr() } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + // override to prevent panic propagation out of function declarations + last = getNode() and cmpl = Done() + } + } + + private class GoStmtTree extends PostOrderTree, GoStmt { + override ControlFlow::Node getNode() { result = MkGoNode(this) } + + override ControlFlowTree getChildTree(int i) { i = 0 and result = getCall() } + } + + private class IfStmtTree extends ControlFlowTree, IfStmt { + private ControlFlow::Node getGuard(boolean outcome) { + result = MkConditionGuardNode(getCond(), outcome) + } + + override predicate firstNode(ControlFlow::Node first) { + firstNode(getInit(), first) + or + not exists(getInit()) and + firstNode(getCond(), first) + } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + ControlFlowTree.super.lastNode(last, cmpl) + or + lastNode(getThen(), last, cmpl) + or + lastNode(getElse(), last, cmpl) + or + not exists(getElse()) and + last = getGuard(false) and + cmpl = Done() + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + lastNode(getInit(), pred, normalCompletion()) and + firstNode(getCond(), succ) + or + exists(Completion condCmpl | + lastNode(getCond(), pred, condCmpl) and + succ = MkConditionGuardNode(getCond(), condCmpl.getOutcome()) + ) + or + pred = getGuard(true) and + firstNode(getThen(), succ) + or + pred = getGuard(false) and + firstNode(getElse(), succ) + } + } + + private class IndexExprTree extends PostOrderTree, IndexExpr { + override ControlFlow::Node getNode() { result = mkExprOrSkipNode(this) } + + override Completion getCompletion() { + result = Done() + or + this.(ReferenceExpr).isRvalue() and + result = Panic() + } + + override ControlFlowTree getChildTree(int i) { + i = 0 and result = getBase() + or + i = 1 and result = getIndex() + } + } + + private class LoopTree extends ControlFlowTree, LoopStmt { + BranchTarget getLabel() { result = BranchTarget::of(this) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + exists(Completion inner | lastNode(getBody(), last, inner) and not inner.isNormal() | + if inner = Break(getLabel()) + then cmpl = Done() + else + if inner = Continue(getLabel()) + then none() + else cmpl = inner + ) + } + } + + private class FileTree extends ControlFlowTree, File { + FileTree() { exists(getADecl()) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { none() } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + ControlFlowTree.super.succ(pred, succ) + or + pred = MkEntryNode(this) and + firstNode(this.getDecl(0), succ) + or + exists(int i, Completion inner | lastNode(this.getDecl(i), pred, inner) | + not inner.isNormal() + or + i = getNumDecl() - 1 + ) and + succ = MkExitNode(this) + } + + override ControlFlowTree getChildTree(int i) { result = getDecl(i) } + } + + private class ForTree extends LoopTree, ForStmt { + private ControlFlow::Node getGuard(boolean outcome) { + result = MkConditionGuardNode(getCond(), outcome) + } + + override predicate firstNode(ControlFlow::Node first) { firstNode(getFirstChildTree(), first) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + LoopTree.super.lastNode(last, cmpl) + or + lastNode(getInit(), last, cmpl) and + not cmpl.isNormal() + or + lastNode(getCond(), last, cmpl) and + not cmpl.isNormal() + or + lastNode(getPost(), last, cmpl) and + not cmpl.isNormal() + or + last = getGuard(false) and + cmpl = Done() + } + + override ControlFlowTree getChildTree(int i) { + i = 0 and result = getInit() + or + i = 1 and result = getCond() + or + i = 2 and result = getBody() + or + i = 3 and result = getPost() + or + i = 4 and result = getCond() + or + i = 5 and result = getBody() + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + exists(int i, ControlFlowTree predTree, Completion cmpl | + predTree = getChildTreeRanked(i) and + lastNode(predTree, pred, cmpl) and + cmpl.isNormal() + | + if predTree = getCond() + then succ = getGuard(cmpl.getOutcome()) + else firstNode(getChildTreeRanked(i + 1), succ) + ) + or + pred = getGuard(true) and + firstNode(getBody(), succ) + } + } + + private class FuncDefTree extends ControlFlowTree, FuncDef { + FuncDefTree() { exists(getBody()) } + + pragma[noinline] + private MkEntryNode getEntry() { + result = MkEntryNode(this) + } + + private ParameterOrReceiver getReceiverOrParameter(int i) { + i = 0 and result.getDeclaration() = this.(MethodDecl).getReceiverDecl().getNameExpr() + or + result = getParameter(i - count(this.(MethodDecl).getReceiverDecl().getNameExpr())) + } + + private ControlFlow::Node getPrologueNode(int i) { + i = -1 and result = getEntry() + or + exists(int numParm, int numRes | + numParm = count(getReceiverOrParameter(_)) and + numRes = count(getResultVar(_)) + | + exists(int j, ParameterOrReceiver p | p = getReceiverOrParameter(j) | + i = 2 * j and result = MkArgumentNode(p) + or + i = 2 * j + 1 and result = MkParameterInit(p) + ) + or + exists(int j, ResultVariable v | v = getResultVar(j) | + i = 2 * numParm + 2 * j and + result = MkZeroInitNode(v) + or + i = 2 * numParm + 2 * j + 1 and + result = MkResultInit(v) + ) + or + i = 2 * numParm + 2 * numRes and + firstNode(getBody(), result) + ) + } + + private ControlFlow::Node getEpilogueNode(int i) { + result = MkResultReadNode(getResultVar(i)) + or + i = count(getAResultVar()) and + result = MkExitNode(this) + } + + pragma[noinline] + private predicate firstDefer(ControlFlow::Node nd) { + exists(DeferStmt defer | + nd = MkExprNode(defer.getCall()) and + // `defer` can be the first `defer` statement executed + // there is always a predecessor node because the `defer`'s call is always + // evaluated before the defer statement itself + MkDeferNode(defer) = succ(notDeferSucc*(getEntry())) + ) + } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { none() } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + exists(int i | + pred = getPrologueNode(i) and + succ = getPrologueNode(i + 1) + ) + or + exists(GotoStmt goto, LabeledStmt ls | + pred = MkSkipNode(goto) and + this = goto.getEnclosingFunction() and + this = ls.getEnclosingFunction() and + goto.getLabel() = ls.getLabel() and + firstNode(ls, succ) + ) + or + exists(Completion cmpl | + lastNode(this.getBody(), pred, cmpl) and + // last node of function body can be reached without going through a `defer` statement + pred = notDeferSucc*(getEntry()) + | + // panic goes directly to exit, non-panic reads result variables first + if cmpl = Panic() then + succ = MkExitNode(this) + else + succ = getEpilogueNode(0) + ) + or + lastNode(this.getBody(), pred, _) and + exists(DeferStmt defer | defer = this.getADeferStmt() | + succ = MkExprNode(defer.getCall()) and + // the last `DeferStmt` executed before pred is this `defer` + pred = notDeferSucc*(MkDeferNode(defer)) + ) + or + exists(DeferStmt predDefer, DeferStmt succDefer | + predDefer = this.getADeferStmt() and + succDefer = this.getADeferStmt() + | + // reversed because `defer`s are executed in LIFO order + MkDeferNode(predDefer) = nextDefer(MkDeferNode(succDefer)) and + pred = MkExprNode(predDefer.getCall()) and + succ = MkExprNode(succDefer.getCall()) + ) + or + firstDefer(pred) and + ( + // conservatively assume that we might either panic (and hence skip the result reads) + // or not + succ = MkExitNode(this) + or + succ = getEpilogueNode(0) + ) + or + exists(int i | + pred = getEpilogueNode(i) and + succ = getEpilogueNode(i+1) + ) + } + } + + private class GotoTree extends ControlFlowTree, GotoStmt { + override predicate firstNode(ControlFlow::Node first) { first = MkSkipNode(this) } + } + + private class IncDecTree extends ControlFlowTree, IncDecStmt { + override predicate firstNode(ControlFlow::Node first) { firstNode(getExpr(), first) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + ControlFlowTree.super.lastNode(last, cmpl) + or + last = MkIncDecNode(this) and + cmpl = Done() + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + lastNode(getExpr(), pred, normalCompletion()) and + succ = MkImplicitOne(this) + or + pred = MkImplicitOne(this) and + succ = MkIncDecRhs(this) + or + pred = MkIncDecRhs(this) and + succ = MkIncDecNode(this) + } + } + + private class RangeTree extends LoopTree, RangeStmt { + override predicate firstNode(ControlFlow::Node first) { firstNode(getDomain(), first) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + LoopTree.super.lastNode(last, cmpl) + or + last = MkNextNode(this) and + cmpl = Done() + or + lastNode(getKey(), last, cmpl) and + not cmpl.isNormal() + or + lastNode(getValue(), last, cmpl) and + not cmpl.isNormal() + or + lastNode(getDomain(), last, cmpl) and + not cmpl.isNormal() + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + lastNode(getDomain(), pred, normalCompletion()) and + succ = MkNextNode(this) + or + pred = MkNextNode(this) and + ( + firstNode(getKey(), succ) + or + not exists(getKey()) and + firstNode(getBody(), succ) + ) + or + lastNode(getKey(), pred, normalCompletion()) and + ( + firstNode(getValue(), succ) + or + not exists(getValue()) and + succ = MkExtractNode(this, 0) + ) + or + lastNode(getValue(), pred, normalCompletion()) and + succ = MkExtractNode(this, 0) + or + pred = MkExtractNode(this, 0) and + ( + if exists(getValue()) + then succ = MkExtractNode(this, 1) + else + if exists(MkAssignNode(this, 0)) + then succ = MkAssignNode(this, 0) + else + if exists(MkAssignNode(this, 1)) + then succ = MkAssignNode(this, 1) + else firstNode(getBody(), succ) + ) + or + pred = MkExtractNode(this, 1) and + ( + if exists(MkAssignNode(this, 0)) + then succ = MkAssignNode(this, 0) + else + if exists(MkAssignNode(this, 1)) + then succ = MkAssignNode(this, 1) + else firstNode(getBody(), succ) + ) + or + pred = MkAssignNode(this, 0) and + ( + if exists(MkAssignNode(this, 1)) + then succ = MkAssignNode(this, 1) + else firstNode(getBody(), succ) + ) + or + pred = MkAssignNode(this, 1) and + firstNode(getBody(), succ) + or + exists(Completion inner | + lastNode(getBody(), pred, inner) and + (inner.isNormal() or inner = Continue(BranchTarget::of(this))) and + succ = MkNextNode(this) + ) + } + } + + private class RecvStmtTree extends ControlFlowTree, RecvStmt { + override predicate firstNode(ControlFlow::Node first) { firstNode(getExpr().getOperand(), first) } + } + + private class ReturnStmtTree extends PostOrderTree, ReturnStmt { + override ControlFlow::Node getNode() { result = MkReturnNode(this) } + + override Completion getCompletion() { result = Return() } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + exists(int i | + lastNode(getExpr(i), pred, normalCompletion()) and + succ = complete(i) + or + pred = MkExtractNode(this, i) and + succ = after(i) + or + pred = MkResultWriteNode(_, i, this) and + succ = next(i) + ) + } + + private ControlFlow::Node complete(int i) { + result = MkExtractNode(this, i) + or + not exists(MkExtractNode(this, _)) and + result = after(i) + } + + private ControlFlow::Node after(int i) { + result = MkResultWriteNode(_, i, this) + or + not exists(MkResultWriteNode(_, i, this)) and + result = next(i) + } + + private ControlFlow::Node next(int i) { + firstNode(getExpr(i+1), result) + or + exists(MkExtractNode(this, _)) and + result = complete(i+1) + or + i+1 = getEnclosingFunction().getType().getNumResult() and + result = getNode() + } + + override ControlFlowTree getChildTree(int i) { result = getExpr(i) } + } + + private class SelectStmtTree extends ControlFlowTree, SelectStmt { + private BranchTarget getLabel() { result = BranchTarget::of(this) } + + override predicate firstNode(ControlFlow::Node first) { + firstNode(getNonDefaultCommClause(0), first) + or + getNumNonDefaultCommClause() = 0 and + first = MkSelectNode(this) + } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + exists(Completion inner | lastNode(getACommClause(), last, inner) | + if inner = Break(getLabel()) then cmpl = Done() else cmpl = inner + ) + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + ControlFlowTree.super.succ(pred, succ) + or + exists(CommClause cc, int i, Stmt comm | + cc = getNonDefaultCommClause(i) and + comm = cc.getComm() and + ( + comm instanceof RecvStmt and + lastNode(comm.(RecvStmt).getExpr().getOperand(), pred, normalCompletion()) + or + comm instanceof SendStmt and + lastNode(comm.(SendStmt).getValue(), pred, normalCompletion()) + ) + | + firstNode(getNonDefaultCommClause(i + 1), succ) + or + i = getNumNonDefaultCommClause() - 1 and + succ = MkSelectNode(this) + ) + or + pred = MkSelectNode(this) and + exists(CommClause cc, Stmt comm | cc = getNonDefaultCommClause(_) and comm = cc.getComm() | + comm instanceof RecvStmt and + succ = MkExprNode(comm.(RecvStmt).getExpr()) + or + comm instanceof SendStmt and + succ = MkSendNode(comm) + ) + or + pred = MkSelectNode(this) and + exists(CommClause cc | cc = getDefaultCommClause() | + firstNode(cc.getStmt(0), succ) + or + succ = MkSkipNode(cc) + ) + or + exists(CommClause cc, RecvStmt recv | cc = getCommClause(_) and recv = cc.getComm() | + pred = MkExprNode(recv.getExpr()) and + ( + firstNode(recv.getLhs(0), succ) + or + not exists(recv.getLhs(0)) and + (firstNode(cc.getStmt(0), succ) or succ = MkSkipNode(cc)) + ) + or + lastNode(recv.getLhs(0), pred, normalCompletion()) and + not exists(recv.getLhs(1)) and + ( + succ = MkAssignNode(recv, 0) + or + not exists(MkAssignNode(recv, 0)) and + (firstNode(cc.getStmt(0), succ) or succ = MkSkipNode(cc)) + ) + or + lastNode(recv.getLhs(1), pred, normalCompletion()) and + succ = MkExtractNode(recv, 0) + or + ( + pred = MkAssignNode(recv, 0) and + not exists(MkExtractNode(recv, 1)) + or + pred = MkExtractNode(recv, 1) and + not exists(MkAssignNode(recv, 1)) + or + pred = MkAssignNode(recv, 1) + ) and + (firstNode(cc.getStmt(0), succ) or succ = MkSkipNode(cc)) + ) + or + exists(CommClause cc, SendStmt ss | + cc = getCommClause(_) and + ss = cc.getComm() and + pred = MkSendNode(ss) + | + firstNode(cc.getStmt(0), succ) + or + succ = MkSkipNode(cc) + ) + } + } + + private class SelectorExprTree extends PostOrderTree, SelectorExpr { + SelectorExprTree() { getBase() instanceof ValueExpr } + + override ControlFlow::Node getNode() { result = mkExprOrSkipNode(this) } + + override Completion getCompletion() { + result = Done() + or + // panic due to `nil` dereference + getBase() instanceof ValueExpr and + this.(ReferenceExpr).isRvalue() and + result = Panic() + } + + override ControlFlowTree getChildTree(int i) { i = 0 and result = getBase() } + } + + private class SendStmtTree extends ControlFlowTree, SendStmt { + override predicate firstNode(ControlFlow::Node first) { firstNode(getChannel(), first) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + ControlFlowTree.super.lastNode(last, cmpl) + or + last = MkSendNode(this) and + (cmpl = Done() or cmpl = Panic()) + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + ControlFlowTree.super.succ(pred, succ) + or + not this = any(CommClause cc).getComm() and + lastNode(getValue(), pred, normalCompletion()) and + succ = MkSendNode(this) + } + + override ControlFlowTree getChildTree(int i) { + i = 0 and result = getChannel() + or + i = 1 and result = getValue() + } + } + + private class SliceExprTree extends ControlFlowTree, SliceExpr { + override predicate firstNode(ControlFlow::Node first) { firstNode(getBase(), first) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + ControlFlowTree.super.lastNode(last, cmpl) + or + last = MkExprNode(this) and + (cmpl = Done() or cmpl = Panic()) + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + ControlFlowTree.super.succ(pred, succ) + or + lastNode(getBase(), pred, normalCompletion()) and + (firstNode(getLow(), succ) or succ = MkImplicitLowerSliceBound(this)) + or + (lastNode(getLow(), pred, normalCompletion()) or pred = MkImplicitLowerSliceBound(this)) and + (firstNode(getHigh(), succ) or succ = MkImplicitUpperSliceBound(this)) + or + (lastNode(getHigh(), pred, normalCompletion()) or pred = MkImplicitUpperSliceBound(this)) and + (firstNode(getMax(), succ) or succ = MkImplicitMaxSliceBound(this)) + or + (lastNode(getMax(), pred, normalCompletion()) or pred = MkImplicitMaxSliceBound(this)) and + succ = MkExprNode(this) + } + } + + private class StarExprTree extends PostOrderTree, StarExpr { + override ControlFlow::Node getNode() { result = mkExprOrSkipNode(this) } + + override Completion getCompletion() { result = Done() or result = Panic() } + + override ControlFlowTree getChildTree(int i) { i = 0 and result = getBase() } + } + + private class SwitchTree extends ControlFlowTree, SwitchStmt { + override predicate firstNode(ControlFlow::Node first) { + firstNode(getInit(), first) + or + not exists(getInit()) and + ( + firstNode(this.(ExpressionSwitchStmt).getExpr(), first) + or + first = MkImplicitTrue(this) + or + firstNode(this.(TypeSwitchStmt).getChildStmt(1), first) + ) + } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + lastNode(getInit(), last, cmpl) and + not cmpl.isNormal() + or + ( + lastNode(this.(ExpressionSwitchStmt).getExpr(), last, cmpl) + or + last = MkImplicitTrue(this) and + cmpl = Bool(true) + or + lastNode(this.(TypeSwitchStmt).getChildStmt(1), last, cmpl) + ) and + ( + not cmpl.isNormal() + or + not exists(this.getDefault()) + ) + or + exists(CaseClause cc, int i, Completion inner | + cc = this.getCase(i) and lastNode(cc, last, inner) + | + not exists(this.getDefault()) and + i = this.getNumCase() - 1 and + last = MkCaseNode(cc, cc.getNumExpr() - 1) and + inner.isNormal() and + cmpl = inner + or + not last instanceof MkCaseNode and + inner.isNormal() and + cmpl = inner + or + if inner = Break(BranchTarget::of(this)) + then cmpl = Done() + else ( + not inner.isNormal() and inner != Fallthrough() and cmpl = inner + ) + ) + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + ControlFlowTree.super.succ(pred, succ) + or + lastNode(getInit(), pred, normalCompletion()) and + ( + firstNode(this.(ExpressionSwitchStmt).getExpr(), succ) or + succ = MkImplicitTrue(this) or + firstNode(this.(TypeSwitchStmt).getChildStmt(1), succ) + ) + or + ( + lastNode(this.(ExpressionSwitchStmt).getExpr(), pred, normalCompletion()) or + pred = MkImplicitTrue(this) or + lastNode(this.(TypeSwitchStmt).getChildStmt(1), pred, normalCompletion()) + ) and + ( + firstNode(getNonDefaultCase(0), succ) + or + not exists(getANonDefaultCase()) and + firstNode(getDefault(), succ) + ) + or + exists(CaseClause cc, int i | + cc = getNonDefaultCase(i) and + lastNode(cc, pred, normalCompletion()) and + pred instanceof MkCaseNode + | + firstNode(getNonDefaultCase(i + 1), succ) + or + i = getNumNonDefaultCase() - 1 and + firstNode(getDefault(), succ) + ) + or + exists(CaseClause cc, int i, CaseClause next | + cc = getCase(i) and + lastNode(cc, pred, Fallthrough()) and + next = getCase(i + 1) + | + firstNode(next.getStmt(0), succ) + or + succ = MkSkipNode(next) + ) + } + } + + private class TypeAssertTree extends PostOrderTree, TypeAssertExpr { + override ControlFlow::Node getNode() { result = MkExprNode(this) } + + override Completion getCompletion() { + result = Done() + or + // panic due to type mismatch, but not if the assertion appears in an assignment or + // initialization with two variables or a type-switch + not exists(Assignment assgn | assgn.getNumLhs() = 2 and this = assgn.getRhs().stripParens()) and + not exists(ValueSpec vs | vs.getNumName() = 2 and this = vs.getInit().stripParens()) and + not exists(TypeSwitchStmt ts | this = ts.getExpr()) and + result = Panic() + } + + override ControlFlowTree getChildTree(int i) { i = 0 and result = getExpression() } + } + + private class UnaryExprTree extends ControlFlowTree, UnaryExpr { + override predicate firstNode(ControlFlow::Node first) { firstNode(getOperand(), first) } + + override predicate lastNode(ControlFlow::Node last, Completion cmpl) { + last = MkExprNode(this) and + ( + cmpl = Done() + or + this instanceof DerefExpr and cmpl = Panic() + ) + } + + override predicate succ(ControlFlow::Node pred, ControlFlow::Node succ) { + ControlFlowTree.super.succ(pred, succ) + or + not this = any(RecvStmt recv).getExpr() and + lastNode(getOperand(), pred, normalCompletion()) and + succ = MkExprNode(this) + } + } + + private ControlFlow::Node mkExprOrSkipNode(Expr e) { + result = MkExprNode(e) or + result = MkSkipNode(e) + } + + cached + predicate firstNode(ControlFlowTree root, ControlFlow::Node first) { root.firstNode(first) } + + cached + predicate lastNode(ControlFlowTree root, ControlFlow::Node last) { + lastNode(root, last, normalCompletion()) + } + + private predicate lastNode(ControlFlowTree root, ControlFlow::Node last, Completion cmpl) { + root.lastNode(last, cmpl) + } + + /** Gets a successor of `nd` that is not a `defer` node */ + private ControlFlow::Node notDeferSucc(ControlFlow::Node nd) { + not result = MkDeferNode(_) and + result = succ(nd) + } + + /** Gets `defer` statements that can be the first defer statement after `nd` in the CFG */ + private ControlFlow::Node nextDefer(ControlFlow::Node nd) { + nd = MkDeferNode(_) and + result = MkDeferNode(_) and + ( + result = succ(nd) + or + result = succ(notDeferSucc+(nd)) + ) + } + + /** Gets a successor of `nd`, that is, a node that is executed after `nd`. */ + cached + ControlFlow::Node succ(ControlFlow::Node nd) { any(ControlFlowTree tree).succ(nd, result) } +} diff --git a/ql/src/semmle/go/controlflow/IR.qll b/ql/src/semmle/go/controlflow/IR.qll new file mode 100644 index 00000000..a8d498fb --- /dev/null +++ b/ql/src/semmle/go/controlflow/IR.qll @@ -0,0 +1,1394 @@ +/** + * Provides classes and predicates for working with an intermediate representation (IR) of Go + * programs that is used as the foundation of the control flow and data flow graphs. + * + * In the IR, the program is represented as a set of instructions, which correspond to expressions + * and statements that compute a value or perform an operation (as opposed to providing syntactic + * structure or type information). + * + * Each instruction is also a control-flow node, but there are control-flow nodes that are not + * instructions (synthetic entry and exit nodes, as well as no-op skip nodes). + */ + +import go +private import semmle.go.controlflow.ControlFlowGraphImpl + +module IR { + /** + * An IR instruction. + */ + class Instruction extends ControlFlow::Node { + Instruction() { + this instanceof MkExprNode or + this instanceof MkLiteralElementInitNode or + this instanceof MkImplicitLiteralElementIndex or + this instanceof MkAssignNode or + this instanceof MkCompoundAssignRhsNode or + this instanceof MkExtractNode or + this instanceof MkZeroInitNode or + this instanceof MkFuncDeclNode or + this instanceof MkDeferNode or + this instanceof MkGoNode or + this instanceof MkConditionGuardNode or + this instanceof MkIncDecNode or + this instanceof MkIncDecRhs or + this instanceof MkImplicitOne or + this instanceof MkReturnNode or + this instanceof MkResultWriteNode or + this instanceof MkResultReadNode or + this instanceof MkSelectNode or + this instanceof MkSendNode or + this instanceof MkParameterInit or + this instanceof MkArgumentNode or + this instanceof MkResultInit or + this instanceof MkNextNode or + this instanceof MkImplicitTrue or + this instanceof MkCaseNode or + this instanceof MkImplicitLowerSliceBound or + this instanceof MkImplicitUpperSliceBound or + this instanceof MkImplicitMaxSliceBound + } + + /** Holds if this instruction reads the value of variable or constant `v`. */ + predicate reads(ValueEntity v) { none() } + + /** Holds if this instruction updates variable or constant `v` to the value of `rhs`. */ + predicate writes(ValueEntity v, Instruction rhs) { none() } + + /** Holds if this instruction reads the value of field `f` on the value of `base`. */ + predicate readsField(Instruction base, Field f) { none() } + + /** Holds if this instruction updates the value of field `f` on the value of `base`. */ + predicate writesField(Instruction base, Field f) { none() } + + /** Holds if this instruction looks up method `m` on the value of `receiver`. */ + predicate readsMethod(Instruction receiver, Method m) { none() } + + /** Holds if this instruction reads the value of element `index` on the value of `base`. */ + predicate readsElement(Instruction base, Instruction index) { none() } + + /** Holds if this instruction updates the value of element `index` on the value of `base`. */ + predicate writesElement(Instruction base, Instruction index) { none() } + + /** Gets the type of the result of this instruction, if any. */ + Type getResultType() { none() } + + /** Gets the float value of the result of this instruction, if it can be determined. */ + float getFloatValue() { none() } + + /** Gets the int value of the result of this instruction, if it can be determined. */ + int getIntValue() { none() } + + /** + * Holds if the complex value of the result of this instruction has real part `real` and + * imaginary part `imag`. + */ + predicate hasComplexValue(float real, float imag) { none() } + + /** Gets either `getFloatValue` or `getIntValue` */ + float getNumericValue() { result = this.getFloatValue() or result = this.getIntValue() } + + /** + * Gets the string representation of the exact value of the result of this instruction, + * if any. + * + * For example, for the constant 3.141592653589793238462, this will + * result in 1570796326794896619231/500000000000000000000 + */ + string getExactValue() { none() } + + /** Gets the string value of the result of this instruction, if it can be determined. */ + string getStringValue() { none() } + + /** Gets the Boolean value of the result of this instruction, if it can be determined. */ + boolean getBoolValue() { none() } + + /** Holds if the result of this instruction is known at compile time. */ + predicate isConst() { none() } + + /** + * Holds if the result of this instruction is known at compile time, and is guaranteed not to + * depend on the platform where it is evaluated. + */ + predicate isPlatformIndependentConstant() { none() } + + /** Gets a textual representation of the kind of this instruction. */ + string getInsnKind() { + this instanceof MkExprNode and result = "expression" or + this instanceof MkLiteralElementInitNode and result = "element init" or + this instanceof MkImplicitLiteralElementIndex and result = "element index" or + this instanceof MkAssignNode and result = "assignment" or + this instanceof MkCompoundAssignRhsNode and result = "right-hand side of compound assignment" or + this instanceof MkExtractNode and result = "tuple element extraction" or + this instanceof MkZeroInitNode and result = "zero value" or + this instanceof MkFuncDeclNode and result = "function declaration" or + this instanceof MkDeferNode and result = "defer" or + this instanceof MkGoNode and result = "go" or + this instanceof MkConditionGuardNode and result = "condition guard" or + this instanceof MkIncDecNode and result = "increment/decrement" or + this instanceof MkIncDecRhs and result = "right-hand side of increment/decrement" or + this instanceof MkImplicitOne and result = "implicit 1" or + this instanceof MkReturnNode and result = "return" or + this instanceof MkResultWriteNode and result = "result write" or + this instanceof MkResultReadNode and result = "result read" or + this instanceof MkSelectNode and result = "select" or + this instanceof MkSendNode and result = "send" or + this instanceof MkParameterInit and result = "parameter initialization" or + this instanceof MkArgumentNode and result = "argument" or + this instanceof MkResultInit and result = "result initialization" or + this instanceof MkNextNode and result = "next key-value pair" or + this instanceof MkImplicitTrue and result = "implicit true" or + this instanceof MkCaseNode and result = "case" or + this instanceof MkImplicitLowerSliceBound and result = "implicit lower bound" or + this instanceof MkImplicitUpperSliceBound and result = "implicit upper bound" or + this instanceof MkImplicitMaxSliceBound and result = "implicit maximum" + } + } + + /** + * An IR instruction representing the evaluation of an expression. + */ + class EvalInstruction extends Instruction, MkExprNode { + Expr e; + + EvalInstruction() { this = MkExprNode(e) } + + /** Gets the expression underlying this instruction. */ + Expr getExpr() { result = e } + + override predicate reads(ValueEntity v) { e = v.getAUse() } + + override Type getResultType() { result = e.getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(e) } + + override float getFloatValue() { result = e.getFloatValue() } + + override int getIntValue() { result = e.getIntValue() } + + override predicate hasComplexValue(float real, float imag) { e.hasComplexValue(real, imag) } + + override string getExactValue() { result = e.getExactValue() } + + override string getStringValue() { result = e.getStringValue() } + + override boolean getBoolValue() { result = e.getBoolValue() } + + override predicate isConst() { e.isConst() } + + override predicate isPlatformIndependentConstant() { e.isPlatformIndependentConstant() } + + override string toString() { result = e.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + e.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An IR instruction that reads the value of a variable, constant, field or array element, + * or refers to a function. + */ + class ReadInstruction extends Instruction { + ReadInstruction() { + exists(Expr e | e = this.(EvalInstruction).getExpr() | + (e instanceof ValueName or e instanceof IndexExpr) and + e.(ReferenceExpr).isRvalue() + ) + or + this instanceof ReadResultInstruction + } + } + + /** + * An IR instruction that reads the value of a field. + * + * On snapshots with incomplete type information, method expressions may sometimes be + * misclassified as field reads. + */ + class FieldReadInstruction extends ReadInstruction, EvalInstruction { + override SelectorExpr e; + + FieldReadInstruction() { + e.getBase() instanceof ValueExpr and + not e.getSelector() = any(Method method).getAReference() + } + + /** Gets the instruction computing the base value on which the field is read. */ + Instruction getBase() { result = evalExprInstruction(e.getBase()) } + + /** Gets the field being read. */ + Field getField() { e.getSelector() = result.getAReference() } + + override predicate readsField(Instruction base, Field f) { + base = getBase() and f = getField() + } + } + + /** + * An IR instruction that looks up a method. + */ + class MethodReadInstruction extends ReadInstruction, EvalInstruction { + Method method; + + override SelectorExpr e; + + MethodReadInstruction() { e.getSelector() = method.getAReference() } + + /** Gets the instruction computing the receiver value on which the method is looked up. */ + Instruction getReceiver() { result = evalExprInstruction(e.getBase()) } + + /** Gets the method being looked up. */ + Method getMethod() { result = method } + + override predicate readsMethod(Instruction receiver, Method m) { + receiver = getReceiver() and m = getMethod() + } + } + + /** + * An IR instruction that reads an element of an array, slice, map or string. + */ + class ElementReadInstruction extends ReadInstruction, EvalInstruction { + override IndexExpr e; + + /** Gets the instruction computing the base value on which the element is looked up. */ + Instruction getBase() { result = evalExprInstruction(e.getBase()) } + + /** Gets the instruction computing the index of the element being looked up. */ + Instruction getIndex() { result = evalExprInstruction(e.getIndex()) } + + override predicate readsElement(Instruction base, Instruction index) { + base = getBase() and index = getIndex() + } + } + + /** + * An IR instruction that writes a memory location. + */ + class WriteInstruction extends Instruction { + WriteTarget lhs; + + WriteInstruction() { + lhs = MkLhs(this, _) + or + lhs = MkLiteralElementTarget(this) + or + lhs = MkResultWriteTarget(this) + } + + /** Gets the target to which this instruction writes. */ + WriteTarget getLhs() { result = lhs } + + /** Gets the instruction computing the value this instruction writes. */ + Instruction getRhs() { none() } + + override predicate writes(ValueEntity v, Instruction rhs) { + getLhs().(VarOrConstTarget).refersTo(v) and + rhs = getRhs() + } + } + + /** + * An IR instruction that initializes a component of a composite literal. + */ + class InitLiteralComponentInstruction extends WriteInstruction, MkLiteralElementInitNode { + CompositeLit lit; + + int i; + + Expr elt; + + InitLiteralComponentInstruction() { this = MkLiteralElementInitNode(elt) and elt = lit.getElement(i) } + + /** Gets the instruction allocating the composite literal. */ + Instruction getBase() { result = evalExprInstruction(lit) } + + override Instruction getRhs() { + result = evalExprInstruction(elt) or + result = evalExprInstruction(elt.(KeyValueExpr).getValue()) + } + + override ControlFlow::Root getRoot() { result.isRootOf(elt) } + + override string toString() { result = "init of " + elt } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + elt.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An IR instruction that initializes a field of a struct literal. + */ + class InitLiteralStructFieldInstruction extends InitLiteralComponentInstruction { + override StructLit lit; + + /** Gets the name of the initialized field. */ + string getFieldName() { + if elt instanceof KeyValueExpr + then result = elt.(KeyValueExpr).getKey().(Ident).getName() + else lit.getStructType().hasOwnField(i, result, _, _) + } + + /** Gets the initialized field. */ + Field getField() { + result.getDeclaringType() = lit.getStructType() and + result.getName() = getFieldName() + } + } + + /** + * An IR instruction that initializes an element of an array, slice or map literal. + */ + class InitLiteralElementInstruction extends InitLiteralComponentInstruction { + Type literalType; + + InitLiteralElementInstruction() { + literalType = lit.getType().getUnderlyingType() and + ( + literalType instanceof ArrayType or + literalType instanceof SliceType or + literalType instanceof MapType + ) + } + + /** Gets the instruction computing the index of the initialized element. */ + Instruction getIndex() { + result = evalExprInstruction(elt.(KeyValueExpr).getKey()) + or + result = MkImplicitLiteralElementIndex(elt) + } + } + + /** + * An IR instruction that initializes an element of an array literal. + */ + class InitLiteralArrayElementInstruction extends InitLiteralElementInstruction { + override ArrayType literalType; + } + + /** + * An IR instruction that initializes an element of a slice literal. + */ + class InitLiteralSliceElementInstruction extends InitLiteralElementInstruction { + override SliceType literalType; + } + + /** + * An IR instruction that initializes an element of a map literal. + */ + class InitLiteralMapElementInstruction extends InitLiteralElementInstruction { + override MapType literalType; + } + + /** + * An IR instruction that writes to a field. + */ + class FieldWriteInstruction extends WriteInstruction { + override FieldTarget lhs; + + /** Gets the instruction computing the base value on which the field is written. */ + Instruction getBase() { result = lhs.getBase() } + + /** Gets the field being written. */ + Field getField() { result = lhs.getField() } + + override predicate writesField(Instruction base, Field f) { + getBase() = base and + getField() = f + } + } + + /** + * An IR instruction that writes to an element of an array, slice, or map. + */ + class ElementWriteInstruction extends WriteInstruction { + override ElementTarget lhs; + + /** Gets the instruction computing the base value on which the field is written. */ + Instruction getBase() { result = lhs.getBase() } + + /** Gets the instruction computing the element index being written. */ + Instruction getIndex() { result = lhs.getIndex() } + + override predicate writesElement(Instruction base, Instruction index) { + getBase() = base and + getIndex() = index + } + } + + /** Holds if `lit` does not specify any explicit keys. */ + private predicate noExplicitKeys(CompositeLit lit) { + not lit.getAnElement() instanceof KeyValueExpr + } + + /** Gets the index of the `i`th element in (array or slice) literal `lit`. */ + private int getElementIndex(CompositeLit lit, int i) { + (lit.getType().getUnderlyingType() instanceof ArrayType or lit.getType().getUnderlyingType() instanceof SliceType) and + exists(Expr elt | elt = lit.getElement(i) | + // short-circuit computation for literals without any explicit keys + noExplicitKeys(lit) and result = i + or + result = elt.(KeyValueExpr).getKey().getIntValue() + or + not elt instanceof KeyValueExpr and + ( + i = 0 and result = 0 + or + result = getElementIndex(lit, i - 1) + 1 + ) + ) + } + + /** + * An IR instruction computing the implicit index of an element in an array or slice literal. + */ + class ImplicitLiteralElementIndexInstruction extends Instruction, MkImplicitLiteralElementIndex { + Expr elt; + + ImplicitLiteralElementIndexInstruction() { this = MkImplicitLiteralElementIndex(elt) } + + override Type getResultType() { result instanceof IntType } + + override ControlFlow::Root getRoot() { result.isRootOf(elt) } + + override int getIntValue() { + exists(CompositeLit lit, int i | elt = lit.getElement(i) | result = getElementIndex(lit, i)) + } + + override string getStringValue() { none() } + + override string getExactValue() { result = getIntValue().toString() } + + override predicate isPlatformIndependentConstant() { any() } + + override predicate isConst() { any() } + + override string toString() { result = "element index" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + elt.hasLocationInfo(filepath, startline, startcolumn, _, _) and + endline = startline and + endcolumn = startcolumn + } + } + + /** + * An instruction assigning to a variable or field. + */ + class AssignInstruction extends WriteInstruction, MkAssignNode { + AstNode assgn; + + int i; + + AssignInstruction() { this = MkAssignNode(assgn, i) } + + override Instruction getRhs() { + exists(SimpleAssignStmt a | a = assgn | + a.getNumLhs() = a.getNumRhs() and + result = evalExprInstruction(a.getRhs(i)) + ) + or + exists(ValueSpec spec | spec = assgn | + spec.getNumName() = spec.getNumInit() and + result = evalExprInstruction(spec.getInit(i)) + or + result = MkZeroInitNode(any(ValueEntity v | spec.getNameExpr(i) = v.getDeclaration())) + ) + or + result = MkCompoundAssignRhsNode(assgn) + or + result = MkExtractNode(assgn, i) + } + + override ControlFlow::Root getRoot() { result.isRootOf(assgn) } + + override string toString() { result = "assignment to " + getLhs() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + getLhs().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** An instruction computing the value of the right-hand side of a compound assignment. */ + class EvalCompoundAssignRhsInstruction extends Instruction, MkCompoundAssignRhsNode { + CompoundAssignStmt assgn; + + EvalCompoundAssignRhsInstruction() { this = MkCompoundAssignRhsNode(assgn) } + + /** Gets the underlying assignment of this instruction. */ + CompoundAssignStmt getAssignment() { result = assgn } + + override Type getResultType() { result = assgn.getRhs().getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(assgn) } + + override string toString() { result = assgn.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + assgn.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction selecting one of multiple values returned by a function, or either the key + * or the value of the iterator in a range loop. + */ + class ExtractTupleElementInstruction extends Instruction, MkExtractNode { + AstNode s; + + int i; + + ExtractTupleElementInstruction() { this = MkExtractNode(s, i) } + + /** Holds if this extracts the `idx`th value of the result of `base`. */ + predicate extractsElement(Instruction base, int idx) { + exists(Expr baseExpr | + baseExpr = s.(Assignment).getRhs() or + baseExpr = s.(ValueSpec).getInit() + | + base = evalExprInstruction(baseExpr) and + idx = i + ) + or + base = MkNextNode(s) and + idx = i + or + base = evalExprInstruction(s.(ReturnStmt).getExpr()) and + idx = i + or + base = evalExprInstruction(s.(CallExpr).getArgument(0)) and + idx = i + } + + override Type getResultType() { + result = s.(Assignment).getLhs(i).getType() + or + result = s.(ValueSpec).getNameExpr(i).getType() + or + i = 0 and + result = s.(RangeStmt).getKey().getType() + or + i = 1 and + result = s.(RangeStmt).getValue().getType() + or + result = s.(ReturnStmt).getEnclosingFunction().getType().getResultType(i) + or + exists(CallExpr inner | + inner = s.(CallExpr).getArgument(0).stripParens() and + result = inner.getTarget().getResultType(i) + ) + } + + override ControlFlow::Root getRoot() { result.isRootOf(s) } + + override string toString() { result = s + "[" + i + "]" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + s.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction that computes the zero value for a variable or constant. + */ + class EvalImplicitInitInstruction extends Instruction, MkZeroInitNode { + ValueEntity v; + + EvalImplicitInitInstruction() { this = MkZeroInitNode(v) } + + override Type getResultType() { result = v.getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(v.getDeclaration()) } + + override int getIntValue() { v.getType().getUnderlyingType() instanceof IntegerType and result = 0 } + + override float getFloatValue() { v.getType().getUnderlyingType() instanceof FloatType and result = 0.0 } + + override string getStringValue() { v.getType().getUnderlyingType() instanceof StringType and result = "" } + + override boolean getBoolValue() { v.getType().getUnderlyingType() instanceof BoolType and result = false } + + override string getExactValue() { + result = getIntValue().toString() or + result = getFloatValue().toString() or + result = getStringValue().toString() or + result = getBoolValue().toString() + } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "zero value for " + v } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + v.getDeclaration().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction that corresponds to the declaration of a function. + */ + class DeclareFunctionInstruction extends Instruction, MkFuncDeclNode { + FuncDecl fd; + + DeclareFunctionInstruction() { this = MkFuncDeclNode(fd) } + + override Type getResultType() { result = fd.getType() } + + override string toString() { result = fd.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + fd.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction that corresponds to a `defer` statement. + */ + class DeferInstruction extends Instruction, MkDeferNode { + DeferStmt defer; + + DeferInstruction() { this = MkDeferNode(defer) } + + override ControlFlow::Root getRoot() { result.isRootOf(defer) } + + override string toString() { result = defer.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + defer.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction that corresponds to a `go` statement. + */ + class GoInstruction extends Instruction, MkGoNode { + GoStmt go; + + GoInstruction() { this = MkGoNode(go) } + + override ControlFlow::Root getRoot() { result.isRootOf(go) } + + override string toString() { result = go.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + go.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction that corresponds to an increment or decrement statement. + */ + class IncDecInstruction extends WriteInstruction, MkIncDecNode { + IncDecStmt ids; + + IncDecInstruction() { this = MkIncDecNode(ids) } + + override Instruction getRhs() { result = MkIncDecRhs(ids) } + + override ControlFlow::Root getRoot() { result.isRootOf(ids) } + + override string toString() { result = ids.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + ids.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction that computes the (implicit) right-hand side of an increment or + * decrement statement. + */ + class EvalIncDecRhsInstruction extends Instruction, MkIncDecRhs { + IncDecStmt ids; + + EvalIncDecRhsInstruction() { this = MkIncDecRhs(ids) } + + /** Gets the corresponding increment or decrement statement. */ + IncDecStmt getStmt() { result = ids } + + override Type getResultType() { result = ids.getExpr().getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(ids) } + + override string toString() { result = "rhs of " + ids } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + ids.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction computing the implicit operand `1` in an increment or decrement statement. + */ + class EvalImplicitOneInstruction extends Instruction, MkImplicitOne { + IncDecStmt ids; + + EvalImplicitOneInstruction() { this = MkImplicitOne(ids) } + + /** Gets the corresponding increment or decrement statement. */ + IncDecStmt getStmt() { result = ids } + + override Type getResultType() { result = ids.getExpr().getType() } + + override ControlFlow::Root getRoot() { result.isRootOf(ids) } + + override int getIntValue() { result = 1 } + + override string getExactValue() { result = "1" } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "1" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + ids.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction corresponding to a return from a function. + */ + class ReturnInstruction extends Instruction, MkReturnNode { + ReturnStmt ret; + + ReturnInstruction() { this = MkReturnNode(ret) } + + /** Holds if this statement returns multiple results. */ + predicate returnsMultipleResults() { + exists(MkExtractNode(ret, _)) or ret.getNumExpr() > 1 + } + + /** Gets the instruction whose result is the (unique) result returned by this statement. */ + Instruction getResult() { + not returnsMultipleResults() and + result = evalExprInstruction(ret.getExpr()) + } + + /** Gets the instruction whose result is the `i`th result returned by this statement. */ + Instruction getResult(int i) { + result = MkExtractNode(ret, i) + or + not exists(MkExtractNode(ret, _)) and + result = evalExprInstruction(ret.getExpr(i)) + } + + override ControlFlow::Root getRoot() { result.isRootOf(ret) } + + override string toString() { result = ret.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + ret.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction that represents the implicit assignment to a result variable + * performed by a return statement. + */ + class WriteResultInstruction extends WriteInstruction, MkResultWriteNode { + ResultVariable var; + + int i; + + ReturnInstruction ret; + + WriteResultInstruction() { + exists(ReturnStmt retstmt | + this = MkResultWriteNode(var, i, retstmt) and + ret = MkReturnNode(retstmt) + ) + } + + override Instruction getRhs() { result = ret.getResult(i) } + + /** Gets the result variable being assigned. */ + ResultVariable getResultVariable() { result = var } + + override Type getResultType() { result = var.getType() } + + override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } + + override string toString() { result = "implicit write of " + var } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + ret.getResult(i).hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction that reads the final value of a result variable upon returning + * from a function. + */ + class ReadResultInstruction extends Instruction, MkResultReadNode { + ResultVariable var; + + ReadResultInstruction() { this = MkResultReadNode(var) } + + override predicate reads(ValueEntity v) { v = var } + + override Type getResultType() { result = var.getType() } + + override ControlFlow::Root getRoot() { var = result.(FuncDef).getAResultVar() } + + override string toString() { result = "implicit read of " + var } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + var.getDeclaration().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction corresponding to a `select` statement. + */ + class SelectInstruction extends Instruction, MkSelectNode { + SelectStmt sel; + + SelectInstruction() { this = MkSelectNode(sel) } + + override ControlFlow::Root getRoot() { result.isRootOf(sel) } + + override string toString() { result = sel.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + sel.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction corresponding to a send statement. + */ + class SendInstruction extends Instruction, MkSendNode { + SendStmt send; + + SendInstruction() { this = MkSendNode(send) } + + override ControlFlow::Root getRoot() { result.isRootOf(send) } + + override string toString() { result = send.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + send.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction initializing a parameter to the corresponding argument. + */ + class InitParameterInstruction extends WriteInstruction, MkParameterInit { + ParameterOrReceiver parm; + + InitParameterInstruction() { this = MkParameterInit(parm) } + + override Instruction getRhs() { result = MkArgumentNode(parm) } + + override ControlFlow::Root getRoot() { result = parm.getFunction() } + + override string toString() { result = "initialization of " + parm } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + parm.getDeclaration().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction reading the value of a function argument. + */ + class ReadArgumentInstruction extends Instruction, MkArgumentNode { + ParameterOrReceiver parm; + + ReadArgumentInstruction() { this = MkArgumentNode(parm) } + + override Type getResultType() { result = parm.getType() } + + override ControlFlow::Root getRoot() { result = parm.getFunction() } + + override string toString() { result = "argument corresponding to " + parm } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + parm.getDeclaration().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction initializing a result variable to its zero value. + */ + class InitResultInstruction extends WriteInstruction, MkResultInit { + ResultVariable res; + + InitResultInstruction() { this = MkResultInit(res) } + + override Instruction getRhs() { result = MkZeroInitNode(res) } + + override ControlFlow::Root getRoot() { result = res.getFunction() } + + override string toString() { result = "initialization of " + res } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + res.getDeclaration().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction that gets the next key-value pair in a range loop. + */ + class GetNextEntryInstruction extends Instruction, MkNextNode { + RangeStmt rs; + + GetNextEntryInstruction() { this = MkNextNode(rs) } + + override ControlFlow::Root getRoot() { result.isRootOf(rs) } + + override string toString() { result = "next key-value pair in range" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + rs.getDomain().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction computing the implicit `true` value in an expression-less `switch` statement. + */ + class EvalImplicitTrueInstruction extends Instruction, MkImplicitTrue { + Stmt stmt; + + EvalImplicitTrueInstruction() { this = MkImplicitTrue(stmt) } + + override Type getResultType() { result instanceof BoolType } + + override ControlFlow::Root getRoot() { result.isRootOf(stmt) } + + override boolean getBoolValue() { result = true } + + override string getExactValue() { result = "true" } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "true" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + stmt.hasLocationInfo(filepath, startline, startcolumn, _, _) and + endline = startline and + endcolumn = startcolumn + } + } + + /** + * An instruction corresponding to a `case` clause. + */ + class CaseInstruction extends Instruction, MkCaseNode { + CaseClause cc; + + int i; + + CaseInstruction() { this = MkCaseNode(cc, i) } + + override ControlFlow::Root getRoot() { result.isRootOf(cc) } + + override string toString() { result = "case " + cc.getExpr(i) } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + cc.getExpr(i).hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction computing the implicit lower slice bound of zero in a slice expression without + * an explicit lower bound. + */ + class EvalImplicitLowerSliceBoundInstruction extends Instruction, MkImplicitLowerSliceBound { + SliceExpr slice; + + EvalImplicitLowerSliceBoundInstruction() { this = MkImplicitLowerSliceBound(slice) } + + override Type getResultType() { result instanceof IntType } + + override ControlFlow::Root getRoot() { result.isRootOf(slice) } + + override int getIntValue() { result = 0 } + + override string getExactValue() { result = "0" } + + override predicate isConst() { any() } + + override predicate isPlatformIndependentConstant() { any() } + + override string toString() { result = "0" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + slice.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction computing the implicit upper slice bound in a slice expression without an + * explicit upper bound. + */ + class EvalImplicitUpperSliceBoundInstruction extends Instruction, MkImplicitUpperSliceBound { + SliceExpr slice; + + EvalImplicitUpperSliceBoundInstruction() { this = MkImplicitUpperSliceBound(slice) } + + override ControlFlow::Root getRoot() { result.isRootOf(slice) } + + override Type getResultType() { result instanceof IntType } + + override string toString() { result = "len" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + slice.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An instruction computing the implicit maximum slice bound in a slice expression without an + * explicit maximum bound. + */ + class EvalImplicitMaxSliceBoundInstruction extends Instruction, MkImplicitMaxSliceBound { + SliceExpr slice; + + EvalImplicitMaxSliceBoundInstruction() { this = MkImplicitMaxSliceBound(slice) } + + override ControlFlow::Root getRoot() { result.isRootOf(slice) } + + override Type getResultType() { result instanceof IntType } + + override string toString() { result = "cap" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + slice.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** A representation of the target of of a write instruction. */ + class WriteTarget extends TWriteTarget { + ControlFlow::Node w; + + WriteTarget() { this = MkLhs(w, _) or this = MkLiteralElementTarget(w) or this = MkResultWriteTarget(w) } + + /** Gets the write instruction of which this is the target. */ + WriteInstruction getWrite() { result = w } + + /** Gets the name of the variable or field being written to, if any. */ + string getName() { none() } + + /** Gets the SSA variable being written to, if any. */ + SsaVariable asSsaVariable() { + getWrite() = result.getDefinition().(SsaExplicitDefinition).getInstruction() + } + + /** Gets a textual representation of this target. */ + string toString() { result = "write target" } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + filepath = "" and startline = 0 and startcolumn = 0 and endline = 0 and endcolumn = 0 + } + } + + /** A reference to a variable or constant, used as the target of a write. */ + class VarOrConstTarget extends WriteTarget { + Expr loc; + + VarOrConstTarget() { + this = MkLhs(_, loc) and + ( + loc instanceof Ident + or + loc instanceof SelectorExpr and + not loc.(SelectorExpr).getBase() instanceof ReferenceExpr + ) + or + exists(WriteResultInstruction wr | + this = MkResultWriteTarget(wr) and + evalExprInstruction(loc) = wr.getRhs() + ) + } + + /** Holds if this is a reference to variable or constant `e`. */ + predicate refersTo(ValueEntity e) { + this instanceof MkLhs and + loc = e.getAReference() + or + exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | + e = wr.getResultVariable() + ) + } + + override string getName() { + this = MkLhs(_, loc) and + ( + result = loc.(Ident).getName() + or + result = loc.(SelectorExpr).getSelector().getName() + ) + or + exists(WriteResultInstruction wr | this = MkResultWriteTarget(wr) | + result = wr.getResultVariable().getName() + ) + } + + /** Gets the variable this refers to, if any. */ + Variable getVariable() { refersTo(result) } + + /** Gets the constant this refers to, if any. */ + Constant getConstant() { refersTo(result) } + + override string toString() { result = getName() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + loc.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** A reference to a field, used as the target of a write. */ + class FieldTarget extends WriteTarget { + FieldTarget() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.getBase() instanceof ValueExpr) + or + w instanceof InitLiteralStructFieldInstruction + } + + /** Gets the instruction computing the base value on which this field is accessed. */ + Instruction getBase() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | + result = evalExprInstruction(sel.getBase()) + ) + or + result = w.(InitLiteralStructFieldInstruction).getBase() + } + + /** Get the type of the base of this field access, that is, the type that contains the field. */ + Type getBaseType() { result = this.getBase().(EvalInstruction).getExpr().getType() } + + /** Holds if this is a reference to variable or constant `e`. */ + predicate refersTo(Field f) { + exists(SelectorExpr sel | this = MkLhs(_, sel) | sel.getSelector() = f.getAReference()) + or + f = w.(InitLiteralStructFieldInstruction).getField() + } + + override string getName() { exists(Field f | this.refersTo(f) | result = f.getName()) } + + /** Gets the field this refers to, if it can be determined. */ + Field getField() { refersTo(result) } + + override string toString() { + exists(SelectorExpr sel | this = MkLhs(_, sel) | + result = "field " + sel.getSelector().getName() + ) + or + result = "field " + w.(InitLiteralStructFieldInstruction).getFieldName() + } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + exists(SelectorExpr sel | this = MkLhs(_, sel) | + sel.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + ) + or + w.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * A reference to an element of an array, slice or map, used as the target of a write. + */ + class ElementTarget extends WriteTarget { + ElementTarget() { + this = MkLhs(_, any(IndexExpr idx)) + or + w instanceof InitLiteralElementInstruction + } + + /** Gets the instruction computing the base value of this element reference. */ + Instruction getBase() { + exists(IndexExpr idx | this = MkLhs(_, idx) | result = evalExprInstruction(idx.getBase())) + or + result = w.(InitLiteralComponentInstruction).getBase() + } + + /** Gets the instruction computing the index of this element reference. */ + Instruction getIndex() { + exists(IndexExpr idx | this = MkLhs(_, idx) | + result = evalExprInstruction(idx.getIndex()) + ) + or + result = w.(InitLiteralElementInstruction).getIndex() + } + + override string toString() { result = "element" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + exists(IndexExpr idx | this = MkLhs(_, idx) | + idx.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + ) + or + w.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * A pointer dereference, used as the target of a write. + */ + class PointerTarget extends WriteTarget { + Expr lhs; + + PointerTarget() { + this = MkLhs(_, lhs) and + (lhs instanceof StarExpr or lhs instanceof DerefExpr) + } + + /** Gets the instruction computing the pointer value being dereferenced. */ + Instruction getBase() { + exists(Expr base | base = lhs.(StarExpr).getBase() or base = lhs.(DerefExpr).getOperand() | + result = evalExprInstruction(base) + ) + } + + override string toString() { result = lhs.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + lhs.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * Gets the (final) instruction computing the value of `e`. + * + * Note that some expressions (such as type expressions or labels) have no corresponding + * instruction, so this predicate is undefined for them. + * + * Short-circuiting expressions that are purely used for control flow (meaning that their + * value is not stored in a variable or used to compute the value of a non-shortcircuiting + * expression) do not have a final instruction either. + */ + Instruction evalExprInstruction(Expr e) { + result = MkExprNode(e) or + result = evalExprInstruction(e.(ParenExpr).getExpression()) + } + + /** + * Gets the instruction corresponding to the initialization of `r`. + */ + InitParameterInstruction initRecvInstruction(ReceiverVariable r) { result = MkParameterInit(r) } + + /** + * Gets the instruction corresponding to the initialization of `p`. + */ + InitParameterInstruction initParamInstruction(Parameter p) { result = MkParameterInit(p) } + + /** + * Gets the instruction corresponding to the `i`th assignment happening at + * `assgn` (0-based). + */ + AssignInstruction assignInstruction(Assignment assgn, int i) { result = MkAssignNode(assgn, i) } + + /** + * Gets the instruction corresponding to the `i`th initialization happening + * at `spec` (0-based). + */ + AssignInstruction initInstruction(ValueSpec spec, int i) { result = MkAssignNode(spec, i) } + + /** + * Gets the instruction corresponding to the assignment of the key variable + * of range statement `rs`. + */ + AssignInstruction assignKeyInstruction(RangeStmt rs) { result = MkAssignNode(rs, 0) } + + /** + * Gets the instruction corresponding to the assignment of the value variable + * of range statement `rs`. + */ + AssignInstruction assignValueInstruction(RangeStmt rs) { result = MkAssignNode(rs, 1) } + + /** + * Gets the instruction corresponding to the implicit initialization of `v` + * to its zero value. + */ + EvalImplicitInitInstruction implicitInitInstruction(ValueEntity v) { result = MkZeroInitNode(v) } + + /** + * Gets the instruction corresponding to the extraction of the `idx`th element + * of the tuple produced by `base`. + */ + ExtractTupleElementInstruction extractTupleElement(Instruction base, int idx) { + result.extractsElement(base, idx) + } +} diff --git a/ql/src/semmle/go/dataflow/DataFlow.qll b/ql/src/semmle/go/dataflow/DataFlow.qll new file mode 100644 index 00000000..6e59fe69 --- /dev/null +++ b/ql/src/semmle/go/dataflow/DataFlow.qll @@ -0,0 +1,25 @@ +/** + * Provides a library for local (intra-procedural) and global (inter-procedural) + * data flow analysis: deciding whether data can flow from a _source_ to a + * _sink_. + * + * Unless configured otherwise, _flow_ means that the exact value of + * the source may reach the sink. We do not track flow across pointer + * dereferences or array indexing. To track these types of flow, where the + * exact value may not be preserved, import + * `semmle.code.go.dataflow.TaintTracking`. + * + * To use global (interprocedural) data flow, extend the class + * `DataFlow::Configuration` as documented on that class. To use local + * (intraprocedural) data flow, invoke `DataFlow::localFlow` or + * `DataFlow::LocalFlowStep` with arguments of type `DataFlow::Node`. + */ + +import go + +module DataFlow { + import semmle.go.dataflow.internal.DataFlowImpl + import Properties +} + +class Read = DataFlow::ReadNode; diff --git a/ql/src/semmle/go/dataflow/FunctionInputsAndOutputs.qll b/ql/src/semmle/go/dataflow/FunctionInputsAndOutputs.qll new file mode 100644 index 00000000..eba33369 --- /dev/null +++ b/ql/src/semmle/go/dataflow/FunctionInputsAndOutputs.qll @@ -0,0 +1,178 @@ +/** + * Provides QL classes for indicating data flow through a function parameter, return value, + * or receiver. + */ + +import go + +/** + * An abstract representation of an input to a function, which is either a parameter + * or the receiver parameter. + */ +private newtype TFunctionInput = + TInParameter(int i) { exists(SignatureType s | exists(s.getParameterType(i))) } + or + TInReceiver() + +/** + * An abstract representation of an input to a function, which is either a parameter + * or the receiver parameter. + */ +class FunctionInput extends TFunctionInput { + /** Holds if this represents the `i`th parameter of a function. */ + predicate isParameter(int i) { + none() + } + + /** Holds if this represents the receiver of a function. */ + predicate isReceiver() { + none() + } + + /** Gets the data-flow node corresponding to this input for the call `c`. */ + final DataFlow::Node getNode(DataFlow::CallNode c) { result = getEntryNode(c) } + + /** Gets the data-flow node through which data is passed into this input for the call `c`. */ + abstract DataFlow::Node getEntryNode(DataFlow::CallNode c); + + /** Gets the data-flow node through which data from this input enters function `f`. */ + abstract DataFlow::Node getExitNode(FuncDef f); + + /** Gets a textual representation of this element. */ + abstract string toString(); +} + +/** A parameter position of a function, viewed as a source of input. */ +private class ParameterInput extends FunctionInput, TInParameter { + int index; + + ParameterInput() { + this = TInParameter(index) + } + + override predicate isParameter(int i) { + i = index + } + + override DataFlow::Node getEntryNode(DataFlow::CallNode c) { + result = c.getArgument(index) + } + + override DataFlow::Node getExitNode(FuncDef f) { + result = DataFlow::parameterNode(f.getParameter(index)) + } + + override string toString() { + result = "parameter " + index + } +} + +/** The receiver of a function, viewed as a source of input. */ +private class ReceiverInput extends FunctionInput, TInReceiver { + override predicate isReceiver() { + any() + } + + override DataFlow::Node getEntryNode(DataFlow::CallNode c) { + result = c.(DataFlow::MethodCallNode).getReceiver() + } + + override DataFlow::Node getExitNode(FuncDef f) { + result = DataFlow::receiverNode(f.(MethodDecl).getReceiver()) + } + + override string toString() { + result = "receiver" + } +} + +/** + * An abstract representation of an output of a function, which is one of its results. + */ +private newtype TFunctionOutput = + TOutResult(int index) { + // the one and only result + index = -1 + or + // one among several results + exists(SignatureType s | exists(s.getResultType(index))) + } + +/** + * An abstract representation of an output of a function, which is one of its results. + */ +class FunctionOutput extends TFunctionOutput { + /** Holds if this represents the (single) result of a function. */ + predicate isResult() { + none() + } + + /** Holds if this represents the `i`th result of a function. */ + predicate isResult(int i) { + none() + } + + /** Gets the data-flow node corresponding to this output for the call `c`. */ + final DataFlow::Node getNode(DataFlow::CallNode c) { result = getExitNode(c) } + + /** Gets the data-flow node through which data is passed into this output for the function `f`. */ + abstract DataFlow::Node getEntryNode(FuncDef f); + + /** Gets the data-flow node through which data is returned from this output for the call `c`. */ + abstract DataFlow::Node getExitNode(DataFlow::CallNode c); + + /** Gets a textual representation of this element. */ + abstract string toString(); +} + +/** A result position of a function, viewed as an output. */ +private class OutResult extends FunctionOutput, TOutResult { + int index; + + OutResult() { + this = TOutResult(index) + } + + override predicate isResult() { + index = -1 + } + + override predicate isResult(int i) { + i = index and i >= 0 + } + + override DataFlow::Node getEntryNode(FuncDef f) { + // return expressions + exists(IR::ReturnInstruction ret | f = ret.getRoot() | + index = -1 and + result = DataFlow::instructionNode(ret.getResult()) + or + index >= 0 and + ret.returnsMultipleResults() and + result = DataFlow::instructionNode(ret.getResult(index)) + ) + or + // expressions assigned to result variables + exists(Write w, int nr | nr = f.getType().getNumResult() | + index = -1 and + nr = 1 and + w.writes(f.getResultVar(0), result) + or + index >= 0 and + nr > 1 and + w.writes(f.getResultVar(index), result) + ) + } + + override DataFlow::Node getExitNode(DataFlow::CallNode c) { + index = -1 and result = c.getResult() + or + result = c.getResult(index) + } + + override string toString() { + index = -1 and result = "result" + or + index >= 0 and result = "result " + index + } +} diff --git a/ql/src/semmle/go/dataflow/GlobalValueNumbering.qll b/ql/src/semmle/go/dataflow/GlobalValueNumbering.qll new file mode 100644 index 00000000..312da3dd --- /dev/null +++ b/ql/src/semmle/go/dataflow/GlobalValueNumbering.qll @@ -0,0 +1,582 @@ +/** + * Provides an implementation of Global Value Numbering. + * See https://en.wikipedia.org/wiki/Global_value_numbering + * + * The predicate `globalValueNumber` converts an expression into a `GVN`, + * which is an abstract type representing the value of the expression. If + * two expressions have the same `GVN` then they compute the same value. + * For example: + * + * ``` + * func f(x int, y int) { + * g(x+y, x+y); + * } + * ``` + * + * In this example, both arguments in the call to `g` compute the same value, + * so both arguments have the same `GVN`. In other words, we can find + * this call with the following query: + * + * ``` + * from CallExpr call, GVN v + * where v = globalValueNumber(call.getArgument(0)) + * and v = globalValueNumber(call.getArgument(1)) + * select call + * ``` + * + * The analysis is conservative, so two expressions might have different + * `GVN`s even though the actually always compute the same value. The most + * common reason for this is that the analysis cannot prove that there + * are no side-effects that might cause the computed value to change. + */ + +/* + * Note to developers: the correctness of this module depends on the + * definitions of GVN, globalValueNumber, and analyzableExpr being kept in + * sync with each other. If you change this module then make sure that the + * change is symmetric across all three. + */ + +import go + +/** + * Holds if the result is a control flow node that might change the + * value of any package variable. This is used in the implementation + * of `MkOtherVariable`, because we need to be quite conservative when + * we assign a value number to a package variable. For example: + * + * ``` + * x = g+1; + * dosomething(); + * y = g+1; + * ``` + * + * It is not safe to assign the same value number to both instances + * of `g+1` in this example, because the call to `dosomething` might + * change the value of `g`. + */ +private ControlFlow::Node nodeWithPossibleSideEffect() { + exists(DataFlow::CallNode call | + call.getCall().mayHaveOwnSideEffects() and + not isPureFn(call.getTarget()) and + result = call.asInstruction() + ) + or + // If the lhs of an assignment is not analyzable by SSA, then + // we need to treat the assignment as having a possible side-effect. + result instanceof Write and + not exists(SsaExplicitDefinition ssa | result = ssa.getInstruction()) +} + +private predicate isPureFn(Function f) { + f.(BuiltinFunction).isPure() + or + isPureStmt(f.(DeclaredFunction).getBody()) +} + +private predicate isPureStmt(Stmt s) { + exists(BlockStmt blk | blk = s | forall(Stmt ch | ch = blk.getAStmt() | isPureStmt(ch))) + or + isPureExpr(s.(ReturnStmt).getExpr()) +} + +private predicate isPureExpr(Expr e) { + e instanceof BasicLit + or + exists(FuncDef f | f = e.getEnclosingFunction() | + e = f.getAParameter().getAReference() + or + e = f.(MethodDecl).getReceiver().getAReference() + ) + or + isPureExpr(e.(SelectorExpr).getBase()) + or + exists(CallExpr ce | e = ce | + isPureFn(ce.getTarget()) and + forall(Expr arg | arg = ce.getAnArgument() | isPureExpr(arg)) + ) +} + +/** + * Gets the entry node of the control flow graph of which `node` is a + * member. + */ +private ControlFlow::Node getControlFlowEntry(ControlFlow::Node node) { + result = node.getRoot().getEntryNode() +} + +private predicate entryNode(ControlFlow::Node node) { node.isEntryNode() } + +/** + * Holds if there is a control flow edge from `src` to `dst` or + * if `dst` is an expression with a possible side-effect. The idea + * is to treat side effects as entry points in the control flow + * graph so that we can use the dominator tree to find the most recent + * side-effect. + */ +private predicate sideEffectCFG(ControlFlow::Node src, ControlFlow::Node dst) { + src.getASuccessor() = dst + or + // Add an edge from the entry point to any node that might have a side + // effect. + dst = nodeWithPossibleSideEffect() and + src = getControlFlowEntry(dst) +} + +/** + * Holds if `dominator` is the immediate dominator of `node` in + * the side-effect CFG. + */ +private predicate iDomEffect(ControlFlow::Node dominator, ControlFlow::Node node) = + idominance(entryNode/1, sideEffectCFG/2)(_, dominator, node) + +/** + * Gets the most recent side effect. To be more precise, `result` is a + * dominator of `node` and no side-effects can occur between `result` and + * `node`. + * + * `sideEffectCFG` has an edge from the function entry to every node with a + * side-effect. This means that every node with a side-effect has the + * function entry as its immediate dominator. So if node `x` dominates node + * `y` then there can be no side effects between `x` and `y` unless `x` is + * the function entry. So the optimal choice for `result` has the function + * entry as its immediate dominator. + * + * Example: + * + * ``` + * 000: int f(int a, int b, int *p) { + * 001: int r = 0; + * 002: if (a) { + * 003: if (b) { + * 004: sideEffect1(); + * 005: } + * 006: } else { + * 007: sideEffect2(); + * 008: } + * 009: if (a) { + * 010: r++; // Not a side-effect, because r is an SSA variable. + * 011: } + * 012: if (b) { + * 013: r++; // Not a side-effect, because r is an SSA variable. + * 014: } + * 015: return *p; + * 016: } + * ``` + * + * Suppose we want to find the most recent side-effect for the dereference + * of `p` on line 015. The `sideEffectCFG` has an edge from the function + * entry (line 000) to the side effects at lines 004 and 007. Therefore, + * the immediate dominator tree looks like this: + * + * 000 - 001 - 002 - 003 + * - 004 + * - 007 + * - 009 - 010 + * - 012 - 013 + * - 015 + * + * The immediate dominator path to line 015 is 000 - 009 - 012 - 015. + * Therefore, the most recent side effect for line 015 is line 009. + */ +cached +private ControlFlow::Node mostRecentSideEffect(ControlFlow::Node node) { + exists(ControlFlow::Node entry | + entryNode(entry) and + iDomEffect(entry, result) and + iDomEffect*(result, node) + ) +} + +/** Used to represent the "global value number" of an expression. */ +cached +private newtype GVNBase = + MkNumericConst(string val) { mkNumericConst(_, val) } or + MkStringConst(string val) { mkStringConst(_, val) } or + MkBoolConst(boolean val) { mkBoolConst(_, val) } or + MkIndirectSsa(SsaDefinition def) { not ssaInit(def, _) } or + MkFunc(Function fn) { mkFunc(_, fn) } or + // Variables with no SSA information. As a crude (but safe) + // approximation, we use `mostRecentSideEffect` to compute a definition + // location for the variable. This ensures that two instances of the same + // global variable will only get the same value number if they are + // guaranteed to have the same value. + MkOtherVariable(ValueEntity x, ControlFlow::Node dominator) { mkOtherVariable(_, x, dominator) } or + MkMethodAccess(GVN base, Function m) { mkMethodAccess(_, base, m) } or + MkFieldRead(GVN base, Variable f, ControlFlow::Node dominator) { + mkFieldRead(_, base, f, dominator) + } or + MkPureCall(Function f, GVN callee, GVNList args) { mkPureCall(_, f, callee, args) } or + MkIndex(GVN base, GVN index, ControlFlow::Node dominator) { mkIndex(_, base, index, dominator) } or + // Dereference a pointer. The value might have changed since the last + // time the pointer was dereferenced, so we need to include a definition + // location. As a crude (but safe) approximation, we use + // `mostRecentSideEffect` to compute a definition location. + MkDeref(GVN base, ControlFlow::Node dominator) { mkDeref(_, base, dominator) } or + MkBinaryOp(GVN lhs, GVN rhs, string op) { mkBinaryOp(_, lhs, rhs, op) } or + MkUnaryOp(GVN child, string op) { mkUnaryOp(_, child, op) } or + // Any expression that is not handled by the cases above is + // given a unique number based on the expression itself. + MkUnanalyzable(DataFlow::Node e) { not analyzableExpr(e) } + +private newtype GVNList = + MkNil() or + MkCons(GVN head, GVNList tail) { globalValueNumbers(_, _, head, tail) } + +private GVNList globalValueNumbers(DataFlow::CallNode ce, int start) { + analyzableCall(ce, _) and + start = ce.getNumArgument() and + result = MkNil() + or + exists(GVN head, GVNList tail | + globalValueNumbers(ce, start, head, tail) and + result = MkCons(head, tail) + ) +} + +private predicate globalValueNumbers(DataFlow::CallNode ce, int start, GVN head, GVNList tail) { + analyzableCall(ce, _) and + head = globalValueNumber(ce.getArgument(start)) and + tail = globalValueNumbers(ce, start + 1) +} + +/** + * A Global Value Number. A GVN is an abstract representation of the value + * computed by an expression. The relationship between `Expr` and `GVN` is + * many-to-one: every `Expr` has exactly one `GVN`, but multiple + * expressions can have the same `GVN`. If two expressions have the same + * `GVN`, it means that they compute the same value at run time. The `GVN` + * is an opaque value, so you cannot deduce what the run-time value of an + * expression will be from its `GVN`. The only use for the `GVN` of an + * expression is to find other expressions that compute the same value. + * Use the predicate `globalValueNumber` to get the `GVN` for an `Expr`. + * + * Note: `GVN` has `toString` and `getLocation` methods, so that it can be + * displayed in a results list. These work by picking an arbitrary + * expression with this `GVN` and using its `toString` and `getLocation` + * methods. + */ +class GVN extends GVNBase { + GVN() { this instanceof GVNBase } + + /** Gets a data-flow node that has this GVN. */ + DataFlow::Node getANode() { this = globalValueNumber(result) } + + /** Gets the kind of the GVN. This can be useful for debugging. */ + string getKind() { + this instanceof MkNumericConst and result = "NumericConst" + or + this instanceof MkStringConst and result = "StringConst" + or + this instanceof MkBoolConst and result = "BoolConst" + or + this instanceof MkIndirectSsa and result = "IndirectSsa" + or + this instanceof MkFunc and result = "Func" + or + this instanceof MkOtherVariable and result = "OtherVariable" + or + this instanceof MkMethodAccess and result = "MethodAccess" + or + this instanceof MkFieldRead and result = "FieldRead" + or + this instanceof MkPureCall and result = "PureCall" + or + this instanceof MkIndex and result = "Index" + or + this instanceof MkDeref and result = "Deref" + or + this instanceof MkBinaryOp and result = "BinaryOp" + or + this instanceof MkUnaryOp and result = "UnaryOp" + or + this instanceof MkUnanalyzable and result = "Unanalyzable" + } + + /** + * Gets an example of a data-flow node with this GVN. + * This is useful for things like implementing toString(). + */ + private DataFlow::Node exampleNode() { + // Pick the expression with the minimum source location. This is + // just an arbitrary way to pick an expression with this `GVN`. + result = min(DataFlow::Node e, string f, int l, int c, string k | + e = getANode() and e.hasLocationInfo(f, l, c, _, _) and k = e.getNodeKind() + | + e order by f, l, c, k + ) + } + + /** Gets a textual representation of this element. */ + string toString() { result = exampleNode().toString() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + exampleNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +private predicate mkNumericConst(DataFlow::Node nd, string val) { + nd.getType().getUnderlyingType() instanceof NumericType and + val = nd.getExactValue() and + nd.isPlatformIndependentConstant() +} + +private predicate mkStringConst(DataFlow::Node nd, string val) { + val = nd.getStringValue() and + nd.isPlatformIndependentConstant() +} + +private predicate mkBoolConst(DataFlow::Node nd, boolean val) { + val = nd.getBoolValue() and + nd.isPlatformIndependentConstant() +} + +private predicate mkFunc(DataFlow::Node nd, Function f) { nd = f.getARead() } + +private predicate analyzableConst(DataFlow::Node e) { + mkNumericConst(e, _) or mkStringConst(e, _) or mkBoolConst(e, _) or mkFunc(e, _) +} + +private predicate analyzableMethodAccess(Read access, DataFlow::Node receiver, Method m) { + access.readsMethod(receiver, m) and + not access.isConst() +} + +private predicate mkMethodAccess(DataFlow::Node access, GVN qualifier, Function m) { + exists(DataFlow::Node base | + analyzableMethodAccess(access, base, m) and + qualifier = globalValueNumber(base) + ) +} + +private predicate analyzableFieldRead(Read fread, DataFlow::Node base, Field f) { + fread.readsField(base, f) and + strictcount(mostRecentSideEffect(fread.asInstruction())) = 1 and + not fread.isConst() +} + +private predicate mkFieldRead( + DataFlow::Node fread, GVN qualifier, Variable v, ControlFlow::Node dominator +) { + exists(DataFlow::Node base | + analyzableFieldRead(fread, base, v) and + qualifier = globalValueNumber(base) and + dominator = mostRecentSideEffect(fread.asInstruction()) + ) +} + +private predicate analyzableCall(DataFlow::CallNode ce, Function f) { + f = ce.getTarget() and + isPureFn(f) and + not ce.isConst() +} + +private predicate mkPureCall(DataFlow::CallNode ce, Function f, GVN callee, GVNList args) { + analyzableCall(ce, f) and + callee = globalValueNumber(ce.getCalleeNode()) and + args = globalValueNumbers(ce, 0) +} + +/** + * Holds if `v` is a variable whose value changes are not, or at least not fully, captured by SSA. + * + * This is the case for package variables (for which no SSA information exists), but also for + * variables of non-primitive type (for which deep mutations are not captured by SSA). + */ +private predicate incompleteSsa(ValueEntity v) { + not v instanceof SsaSourceVariable + or + v.(SsaSourceVariable).mayHaveIndirectReferences() + or + exists(Type tp | tp = v.(DeclaredVariable).getType().getUnderlyingType() | + not tp instanceof BasicType + ) +} + +/** + * Holds if `access` is an access to a variable `target` for which SSA information is incomplete. + */ +private predicate analyzableOtherVariable(DataFlow::Node access, ValueEntity target) { + access.asInstruction().reads(target) and + incompleteSsa(target) and + strictcount(mostRecentSideEffect(access.asInstruction())) = 1 and + not access.isConst() and + not target instanceof Function +} + +private predicate mkOtherVariable(DataFlow::Node access, ValueEntity x, ControlFlow::Node dominator) { + analyzableOtherVariable(access, x) and + dominator = mostRecentSideEffect(access.asInstruction()) +} + +private predicate analyzableBinaryOp( + DataFlow::BinaryOperationNode op, string opname, DataFlow::Node lhs, DataFlow::Node rhs +) { + opname = op.getOperator() and + not op.mayHaveSideEffects() and + lhs = op.getLeftOperand() and + rhs = op.getRightOperand() and + not op.isConst() +} + +private predicate mkBinaryOp(DataFlow::Node op, GVN lhs, GVN rhs, string opname) { + exists(DataFlow::Node l, DataFlow::Node r | + analyzableBinaryOp(op, opname, l, r) and + lhs = globalValueNumber(l) and + rhs = globalValueNumber(r) + ) +} + +private predicate analyzableUnaryOp(DataFlow::UnaryOperationNode op) { + not op.mayHaveSideEffects() and + not op.isConst() +} + +private predicate mkUnaryOp(DataFlow::UnaryOperationNode op, GVN child, string opname) { + analyzableUnaryOp(op) and + child = globalValueNumber(op.getOperand()) and + opname = op.getOperator() +} + +private predicate analyzableIndexExpr(DataFlow::ElementReadNode ae) { + strictcount(mostRecentSideEffect(ae.asInstruction())) = 1 and + not ae.isConst() +} + +private predicate mkIndex( + DataFlow::ElementReadNode ae, GVN base, GVN offset, ControlFlow::Node dominator +) { + analyzableIndexExpr(ae) and + base = globalValueNumber(ae.getBase()) and + offset = globalValueNumber(ae.getIndex()) and + dominator = mostRecentSideEffect(ae.asInstruction()) +} + +private predicate analyzablePointerDereferenceExpr(DataFlow::PointerDereferenceNode deref) { + strictcount(mostRecentSideEffect(deref.asInstruction())) = 1 and + not deref.isConst() +} + +private predicate mkDeref(DataFlow::PointerDereferenceNode deref, GVN p, ControlFlow::Node dominator) { + analyzablePointerDereferenceExpr(deref) and + p = globalValueNumber(deref.getOperand()) and + dominator = mostRecentSideEffect(deref.asInstruction()) +} + +private predicate ssaInit(SsaExplicitDefinition ssa, DataFlow::Node rhs) { + ssa.getRhs() = rhs.asInstruction() +} + +/** Gets the global value number of data-flow node `nd`. */ +cached +GVN globalValueNumber(DataFlow::Node nd) { + exists(string val | + mkNumericConst(nd, val) and + result = MkNumericConst(val) + ) + or + exists(string val | + mkStringConst(nd, val) and + result = MkStringConst(val) + ) + or + exists(boolean val | + mkBoolConst(nd, val) and + result = MkBoolConst(val) + ) + or + exists(Function f | + mkFunc(nd, f) and + result = MkFunc(f) + ) + or + exists(ValueEntity x, ControlFlow::Node dominator | + mkOtherVariable(nd, x, dominator) and + result = MkOtherVariable(x, dominator) + ) + or + exists(GVN qualifier, Function target | + mkMethodAccess(nd, qualifier, target) and + result = MkMethodAccess(qualifier, target) + ) + or + exists(GVN qualifier, Entity target, ControlFlow::Node dominator | + mkFieldRead(nd, qualifier, target, dominator) and + result = MkFieldRead(qualifier, target, dominator) + ) + or + exists(Function f, GVN callee, GVNList args | + mkPureCall(nd, f, callee, args) and + result = MkPureCall(f, callee, args) + ) + or + exists(GVN lhs, GVN rhs, string opname | + mkBinaryOp(nd, lhs, rhs, opname) and + result = MkBinaryOp(lhs, rhs, opname) + ) + or + exists(GVN child, string opname | + mkUnaryOp(nd, child, opname) and + result = MkUnaryOp(child, opname) + ) + or + exists(GVN x, GVN i, ControlFlow::Node dominator | + mkIndex(nd, x, i, dominator) and + result = MkIndex(x, i, dominator) + ) + or + exists(GVN p, ControlFlow::Node dominator | + mkDeref(nd, p, dominator) and + result = MkDeref(p, dominator) + ) + or + not analyzableExpr(nd) and + result = MkUnanalyzable(nd) + or + exists(DataFlow::SsaNode ssa | + nd = ssa.getAUse() and + not incompleteSsa(ssa.getSourceVariable()) and + result = globalValueNumber(ssa) + ) + or + exists(SsaDefinition ssa | ssa = nd.(DataFlow::SsaNode).getDefinition() | + // Local variable with a defining value. + exists(DataFlow::Node init | + ssaInit(ssa, init) and + result = globalValueNumber(init) + ) + or + // Local variable without a defining value. + not ssaInit(ssa, _) and + result = MkIndirectSsa(ssa) + ) +} + +/** + * Holds if the expression is explicitly handled by `globalValueNumber`. + * Unanalyzable expressions still need to be given a global value number, + * but it will be a unique number that is not shared with any other + * expression. + */ +private predicate analyzableExpr(DataFlow::Node e) { + analyzableConst(e) or + any(DataFlow::SsaNode ssa).getAUse() = e or + e instanceof DataFlow::SsaNode or + analyzableOtherVariable(e, _) or + analyzableMethodAccess(e, _, _) or + analyzableFieldRead(e, _, _) or + analyzableCall(e, _) or + analyzableBinaryOp(e, _, _, _) or + analyzableUnaryOp(e) or + analyzableIndexExpr(e) or + analyzablePointerDereferenceExpr(e) +} diff --git a/ql/src/semmle/go/dataflow/Properties.qll b/ql/src/semmle/go/dataflow/Properties.qll new file mode 100644 index 00000000..c030f384 --- /dev/null +++ b/ql/src/semmle/go/dataflow/Properties.qll @@ -0,0 +1,60 @@ +/** + * Provides a class for representing and reasoning about properties of data-flow nodes. + */ + +import go + +private newtype TProperty = + IsBoolean(Boolean b) or + IsNil(Boolean b) + +/** + * A property which may or may not hold of a data-flow node. + * + * Supported properties currently are Boolean truth and `nil`-ness. + */ +class Property extends TProperty { + /** + * Holds if `test` evaluating to `outcome` means that this property holds of `nd`. + */ + predicate checkOn(DataFlow::Node test, Boolean outcome, DataFlow::Node nd) { + exists(EqualityTestExpr eq, Expr e, boolean isTrue | + eq = test.asExpr() and eq.hasOperands(nd.asExpr(), e) + | + this = IsBoolean(isTrue) and + isTrue = eq.getPolarity().booleanXor(e.getBoolValue().booleanXor(outcome)) + or + this = IsNil(isTrue) and + e = Builtin::nil().getAReference() and + isTrue = eq.getPolarity().booleanXor(outcome).booleanNot() + ) + or + test = nd and + test.asExpr() instanceof ValueExpr and + test.getType().getUnderlyingType() instanceof BoolType and + this = IsBoolean(outcome) + } + + /** Holds if this is the property of having the Boolean value `b`. */ + predicate isBoolean(boolean b) { this = IsBoolean(b) } + + /** Holds if this is the property of being `nil`. */ + predicate isNil() { this = IsNil(true) } + + /** Holds if this is the property of being non-`nil`. */ + predicate isNonNil() { this = IsNil(false) } + + /** Gets a textual representation of this property. */ + string toString() { + exists(boolean b | + this = IsBoolean(b) and + result = "is " + b + ) + or + this = IsNil(true) and + result = "is nil" + or + this = IsNil(false) and + result = "is not nil" + } +} diff --git a/ql/src/semmle/go/dataflow/SSA.qll b/ql/src/semmle/go/dataflow/SSA.qll new file mode 100644 index 00000000..d58aec96 --- /dev/null +++ b/ql/src/semmle/go/dataflow/SSA.qll @@ -0,0 +1,285 @@ +/** + * Provides classes for working with static single assignment form (SSA). + */ + +import go +private import SsaImpl + +/** + * A variable that can be SSA converted, that is, a local variable, but not a variable + * declared in file scope. + */ +class SsaSourceVariable extends LocalVariable { + SsaSourceVariable() { + not getScope() instanceof FileScope + } + + /** + * Holds if there may be indirect references of this variable that are not covered by `getAReference()`. + * + * This is the case for variables that have their address taken, and for variables whose + * name resolution information may be incomplete (for instance due to an extractor error). + */ + predicate mayHaveIndirectReferences() { + // variables that have their address taken + exists(AddressExpr addr | addr.getOperand().stripParens() = getAUse()) + or + // variables where there is an unresolved reference with the same name in the same + // scope or a nested scope, suggesting that name resolution information may be incomplete + exists(FunctionScope scope, FuncDef inner | + scope = this.getScope().(LocalScope).getEnclosingFunctionScope() and + unresolvedReference(getName(), inner) and + inner.getScope().getOuterScope*() = scope + ) + } +} + +/** + * Holds if there is an unresolved reference to `name` in `fn`. + */ +private predicate unresolvedReference(string name, FuncDef fn) { + exists(Ident unresolved | + unresolved.getName() = name and + unresolved instanceof ReferenceExpr and + not unresolved = any(SelectorExpr sel).getSelector() and + not unresolved.refersTo(_) and + fn = unresolved.getEnclosingFunction() + ) +} + +/** + * An SSA variable. + */ +class SsaVariable extends TSsaDefinition { + /** Gets the source variable corresponding to this SSA variable. */ + SsaSourceVariable getSourceVariable() { result = this.(SsaDefinition).getSourceVariable() } + + /** Gets the (unique) definition of this SSA variable. */ + SsaDefinition getDefinition() { result = this } + + /** Gets the type of this SSA variable. */ + Type getType() { result = getSourceVariable().getType() } + + /** Gets a use in basic block `bb` that refers to this SSA variable. */ + IR::Instruction getAUseIn(ReachableBasicBlock bb) { + exists(int i, SsaSourceVariable v | v = getSourceVariable() | + result = bb.getNode(i) and + this = getDefinition(bb, i, v) + ) + } + + /** Gets a use that refers to this SSA variable. */ + IR::Instruction getAUse() { result = getAUseIn(_) } + + /** Gets a textual representation of this element. */ + string toString() { result = getDefinition().prettyPrintRef() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + getDefinition().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** + * An SSA definition. + */ +class SsaDefinition extends TSsaDefinition { + /** Gets the SSA variable defined by this definition. */ + SsaVariable getVariable() { result = this } + + /** Gets the source variable defined by this definition. */ + abstract SsaSourceVariable getSourceVariable(); + + /** + * Gets the basic block to which this definition belongs. + */ + abstract ReachableBasicBlock getBasicBlock(); + + /** + * INTERNAL: Use `getBasicBlock()` and `getSourceVariable()` instead. + * + * Holds if this is a definition of source variable `v` at index `idx` in basic block `bb`. + * + * Phi nodes are considered to be at index `-1`, all other definitions at the index of + * the control flow node they correspond to. + */ + abstract predicate definesAt(ReachableBasicBlock bb, int idx, SsaSourceVariable v); + + /** + * INTERNAL: Use `toString()` instead. + * + * Gets a pretty-printed representation of this SSA definition. + */ + abstract string prettyPrintDef(); + + /** + * INTERNAL: Do not use. + * + * Gets a pretty-printed representation of a reference to this SSA definition. + */ + abstract string prettyPrintRef(); + + /** Gets the innermost function or file to which this SSA definition belongs. */ + ControlFlow::Root getRoot() { result = getBasicBlock().getRoot() } + + /** Gets a textual representation of this element. */ + string toString() { result = prettyPrintDef() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + abstract predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ); +} + +/** + * An SSA definition that corresponds to an explicit assignment or other variable definition. + */ +class SsaExplicitDefinition extends SsaDefinition, TExplicitDef { + IR::Instruction getInstruction() { + exists(BasicBlock bb, int i | this = TExplicitDef(bb, i, _) | result = bb.getNode(i)) + } + + IR::Instruction getRhs() { getInstruction().writes(_, result) } + + override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + this = TExplicitDef(bb, i, v) + } + + override ReachableBasicBlock getBasicBlock() { definesAt(result, _, _) } + + override SsaSourceVariable getSourceVariable() { this = TExplicitDef(_, _, result) } + + override string prettyPrintRef() { + exists(int l, int c | hasLocationInfo(_, l, c, _, _) | result = "def@" + l + ":" + c) + } + + override string prettyPrintDef() { result = "definition of " + getSourceVariable() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + getInstruction().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +module SsaExplicitDefinition { + /** + * Gets the SSA definition corresponding to definition `def`. + */ + SsaExplicitDefinition of(IR::Instruction def) { result.getInstruction() = def } +} + +/** + * An SSA definition that does not correspond to an explicit variable definition. + */ +abstract class SsaImplicitDefinition extends SsaDefinition { + /** + * INTERNAL: Do not use. + * + * Gets the definition kind to include in `prettyPrintRef`. + */ + abstract string getKind(); + + override string prettyPrintRef() { + exists(int l, int c | hasLocationInfo(_, l, c, _, _) | result = getKind() + "@" + l + ":" + c) + } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + endline = startline and + endcolumn = startcolumn and + getBasicBlock().hasLocationInfo(filepath, startline, startcolumn, _, _) + } +} + +/** + * An SSA definition representing the capturing of an SSA-convertible variable + * in the closure of a nested function. + * + * Capturing definitions appear at the beginning of such functions, as well as + * at any function call that may affect the value of the variable. + */ +class SsaVariableCapture extends SsaImplicitDefinition, TCapture { + override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + this = TCapture(bb, i, v) + } + + override ReachableBasicBlock getBasicBlock() { definesAt(result, _, _) } + + override SsaSourceVariable getSourceVariable() { definesAt(_, _, result) } + + override string getKind() { result = "capture" } + + override string prettyPrintDef() { result = "capture variable " + getSourceVariable() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + exists(ReachableBasicBlock bb, int i | definesAt(bb, i, _) | + bb.getNode(i).hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + ) + } +} + +/** + * An SSA definition such as a phi node that has no actual semantics, but simply serves to + * merge or filter data flow. + */ +abstract class SsaPseudoDefinition extends SsaImplicitDefinition { + /** + * Gets an input of this pseudo-definition. + */ + abstract SsaVariable getAnInput(); + + /** + * Gets a textual representation of the inputs of this pseudo-definition + * in lexicographical order. + */ + string ppInputs() { result = concat(getAnInput().getDefinition().prettyPrintRef(), ", ") } +} + +/** + * An SSA phi node, that is, a pseudo-definition for a variable at a point + * in the flow graph where otherwise two or more definitions for the variable + * would be visible. + */ +class SsaPhiNode extends SsaPseudoDefinition, TPhi { + override SsaVariable getAnInput() { + result = getDefReachingEndOf(getBasicBlock().getAPredecessor(), getSourceVariable()) + } + + override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + bb = getBasicBlock() and v = getSourceVariable() and i = -1 + } + + override ReachableBasicBlock getBasicBlock() { this = TPhi(result, _) } + + override SsaSourceVariable getSourceVariable() { this = TPhi(_, result) } + + override string getKind() { result = "phi" } + + override string prettyPrintDef() { result = getSourceVariable() + " = phi(" + ppInputs() + ")" } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + endline = startline and + endcolumn = startcolumn and + getBasicBlock().hasLocationInfo(filepath, startline, startcolumn, _, _) + } +} diff --git a/ql/src/semmle/go/dataflow/SsaImpl.qll b/ql/src/semmle/go/dataflow/SsaImpl.qll new file mode 100644 index 00000000..b00dd4bc --- /dev/null +++ b/ql/src/semmle/go/dataflow/SsaImpl.qll @@ -0,0 +1,279 @@ +/** + * INTERNAL: Analyses should use module `SSA` instead. + * + * Provides predicates for constructing an SSA representation for functions. + */ + +import go + +cached +private module Internal { + /** Holds if the `i`th node of `bb` defines `v`. */ + cached + predicate defAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + bb.getNode(i).(IR::Instruction).writes(v, _) + } + + /** Holds if the `i`th node of `bb` reads `v`. */ + cached + predicate useAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + bb.getNode(i).(IR::Instruction).reads(v) + } + + /** + * A data type representing SSA definitions. + * + * We distinguish three kinds of SSA definitions: + * + * 1. Variable definitions, including declarations, assignments and increments/decrements. + * 2. Pseudo-definitions for captured variables at the beginning of the capturing function + * as well as after calls. + * 3. Phi nodes. + * + * SSA definitions are only introduced where necessary. In particular, + * unreachable code has no SSA definitions associated with it, and neither + * have dead assignments (that is, assignments whose value is never read). + */ + cached + newtype TSsaDefinition = + TExplicitDef(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + defAt(bb, i, v) and + (liveAfterDef(bb, i, v) or v.isCaptured()) + } or + TCapture(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + mayCapture(bb, i, v) and + liveAfterDef(bb, i, v) + } or + TPhi(ReachableJoinBlock bb, SsaSourceVariable v) { + liveAtEntry(bb, v) and + inDefDominanceFrontier(bb, v) + } + + /** + * Holds if `bb` is in the dominance frontier of a block containing a definition of `v`. + */ + pragma[noinline] + private predicate inDefDominanceFrontier(ReachableJoinBlock bb, SsaSourceVariable v) { + exists(ReachableBasicBlock defbb, SsaDefinition def | + def.definesAt(defbb, _, v) and + bb.inDominanceFrontierOf(defbb) + ) + } + + /** + * Holds if `v` is a captured variable which is declared in `declFun` and read in `useFun`. + */ + private predicate readsCapturedVar(FuncDef useFun, SsaSourceVariable v, FuncDef declFun) { + declFun = v.getDeclaringFunction() and + useFun = any(IR::Instruction u | u.reads(v)).getRoot() and + v.isCaptured() + } + + /** Holds if the `i`th node of `bb` in function `f` is an entry node. */ + private predicate entryNode(FuncDef f, ReachableBasicBlock bb, int i) { + f = bb.getRoot() and + bb.getNode(i).isEntryNode() + } + + /** + * Holds if the `i`th node of `bb` in function `f` is a function call. + */ + private predicate callNode(FuncDef f, ReachableBasicBlock bb, int i) { + f = bb.getRoot() and + bb.getNode(i).(IR::EvalInstruction).getExpr() instanceof CallExpr + } + + /** + * Holds if the `i`th node of basic block `bb` may induce a pseudo-definition for + * modelling updates to captured variable `v`. Whether the definition is actually + * introduced depends on whether `v` is live at this point in the program. + */ + private predicate mayCapture(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + exists(FuncDef capturingContainer, FuncDef declContainer | + // capture initial value of variable declared in enclosing scope + readsCapturedVar(capturingContainer, v, declContainer) and + capturingContainer != declContainer and + entryNode(capturingContainer, bb, i) + or + // re-capture value of variable after a call if it is assigned non-locally + readsCapturedVar(capturingContainer, v, declContainer) and + assignedThroughClosure(v) and + callNode(capturingContainer, bb, i) + ) + } + + /** A classification of variable references into reads and writes. */ + private newtype RefKind = + ReadRef() or + WriteRef() + + /** + * Holds if the `i`th node of basic block `bb` is a reference to `v`, either a read + * (when `tp` is `ReadRef()`) or a direct or indirect write (when `tp` is `WriteRef()`). + */ + private predicate ref(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind tp) { + useAt(bb, i, v) and tp = ReadRef() + or + (mayCapture(bb, i, v) or defAt(bb, i, v)) and + tp = WriteRef() + } + + /** + * Gets the (1-based) rank of the reference to `v` at the `i`th node of basic block `bb`, + * which has the given reference kind `tp`. + */ + private int refRank(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind tp) { + i = rank[result](int j | ref(bb, j, v, _)) and + ref(bb, i, v, tp) + } + + /** + * Gets the maximum rank among all references to `v` in basic block `bb`. + */ + private int maxRefRank(ReachableBasicBlock bb, SsaSourceVariable v) { + result = max(refRank(bb, _, v, _)) + } + + /** + * Holds if variable `v` is live after the `i`th node of basic block `bb`, where + * `i` is the index of a node that may assign or capture `v`. + * + * For the purposes of this predicate, function calls are considered as writes of captured variables. + */ + private predicate liveAfterDef(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + exists(int r | r = refRank(bb, i, v, WriteRef()) | + // the next reference to `v` inside `bb` is a read + r + 1 = refRank(bb, _, v, ReadRef()) + or + // this is the last reference to `v` inside `bb`, but `v` is live at entry + // to a successor basic block of `bb` + r = maxRefRank(bb, v) and + liveAtSuccEntry(bb, v) + ) + } + + /** + * Holds if variable `v` is live at the beginning of basic block `bb`. + * + * For the purposes of this predicate, function calls are considered as writes of captured variables. + */ + private predicate liveAtEntry(ReachableBasicBlock bb, SsaSourceVariable v) { + // the first reference to `v` inside `bb` is a read + refRank(bb, _, v, ReadRef()) = 1 + or + // there is no reference to `v` inside `bb`, but `v` is live at entry + // to a successor basic block of `bb` + not exists(refRank(bb, _, v, _)) and + liveAtSuccEntry(bb, v) + } + + /** + * Holds if `v` is live at the beginning of any successor of basic block `bb`. + */ + private predicate liveAtSuccEntry(ReachableBasicBlock bb, SsaSourceVariable v) { + liveAtEntry(bb.getASuccessor(), v) + } + + /** + * Holds if `v` is assigned outside its declaring function. + */ + private predicate assignedThroughClosure(SsaSourceVariable v) { + any(IR::Instruction def | def.writes(v, _)).getRoot() != v.getDeclaringFunction() + } + + /** + * Holds if the `i`th node of `bb` is a use or an SSA definition of variable `v`, with + * `k` indicating whether it is the former or the latter. + */ + private predicate ssaRef(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind k) { + useAt(bb, i, v) and k = ReadRef() + or + any(SsaDefinition def).definesAt(bb, i, v) and k = WriteRef() + } + + /** + * Gets the (1-based) rank of the `i`th node of `bb` among all SSA definitions + * and uses of `v` in `bb`, with `k` indicating whether it is a definition or a use. + * + * For example, if `bb` is a basic block with a phi node for `v` (considered + * to be at index -1), uses `v` at node 2 and defines it at node 5, we have: + * + * ``` + * ssaRefRank(bb, -1, v, WriteRef()) = 1 // phi node + * ssaRefRank(bb, 2, v, ReadRef()) = 2 // use at node 2 + * ssaRefRank(bb, 5, v, WriteRef()) = 3 // definition at node 5 + * ``` + */ + private int ssaRefRank(ReachableBasicBlock bb, int i, SsaSourceVariable v, RefKind k) { + i = rank[result](int j | ssaRef(bb, j, v, _)) and + ssaRef(bb, i, v, k) + } + + /** + * Gets the minimum rank of a read in `bb` such that all references to `v` between that + * read and the read at index `i` are reads (and not writes). + */ + private int rewindReads(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + exists(int r | r = ssaRefRank(bb, i, v, ReadRef()) | + exists(int j, RefKind k | r - 1 = ssaRefRank(bb, j, v, k) | + k = ReadRef() and result = rewindReads(bb, j, v) + or + k = WriteRef() and result = r + ) + or + r = 1 and result = r + ) + } + + /** + * Gets the SSA definition of `v` in `bb` that reaches the read of `v` at node `i`, if any. + */ + private SsaDefinition getLocalDefinition(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + exists(int r | r = rewindReads(bb, i, v) | + exists(int j | result.definesAt(bb, j, v) and ssaRefRank(bb, j, v, _) = r - 1) + ) + } + + /** + * Gets an SSA definition of `v` that reaches the end of the immediate dominator of `bb`. + */ + pragma[noinline] + private SsaDefinition getDefReachingEndOfImmediateDominator( + ReachableBasicBlock bb, SsaSourceVariable v + ) { + result = getDefReachingEndOf(bb.getImmediateDominator(), v) + } + + /** + * Gets an SSA definition of `v` that reaches the end of basic block `bb`. + */ + cached + SsaDefinition getDefReachingEndOf(ReachableBasicBlock bb, SsaSourceVariable v) { + exists(int lastRef | lastRef = max(int i | ssaRef(bb, i, v, _)) | + result = getLocalDefinition(bb, lastRef, v) + or + result.definesAt(bb, lastRef, v) and + liveAtSuccEntry(bb, v) + ) + or + // In SSA form, the (unique) reaching definition of a use is the closest + // definition that dominates the use. If two definitions dominate a node + // then one must dominate the other, so we can find the reaching definition + // by following the idominance relation backwards. + result = getDefReachingEndOfImmediateDominator(bb, v) and + not exists(SsaDefinition ssa | ssa.definesAt(bb, _, v)) and + liveAtSuccEntry(bb, v) + } + + /** + * Gets the unique SSA definition of `v` whose value reaches the `i`th node of `bb`, + * which is a use of `v`. + */ + cached + SsaDefinition getDefinition(ReachableBasicBlock bb, int i, SsaSourceVariable v) { + result = getLocalDefinition(bb, i, v) + or + rewindReads(bb, i, v) = 1 and result = getDefReachingEndOf(bb.getImmediateDominator(), v) + } +} +import Internal diff --git a/ql/src/semmle/go/dataflow/TaintTracking.qll b/ql/src/semmle/go/dataflow/TaintTracking.qll new file mode 100644 index 00000000..c751d6d3 --- /dev/null +++ b/ql/src/semmle/go/dataflow/TaintTracking.qll @@ -0,0 +1,128 @@ +/** + * Provides classes for performing local (intra-procedural) and + * global (inter-procedural) taint-tracking analyses. + */ + +import go + +module TaintTracking { + private import semmle.go.dataflow.internal.DataFlowPrivate + private import semmle.go.dataflow.FunctionInputsAndOutputs + + /** + * Holds if taint propagates from `source` to `sink` in zero or more local + * (intra-procedural) steps. + */ + predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) } + + /** + * Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local + * (intra-procedural) step. + */ + predicate localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { + // Ordinary data flow + DataFlow::localFlowStep(nodeFrom, nodeTo) + or + taintStep(nodeFrom, nodeTo) + } + + /** + * A taint tracking configuration. + * + * A taint tracking configuration is a special data flow configuration + * (`DataFlow::Configuration`) that allows for flow through nodes that do not + * necessarily preserve values, but are still relevant from a taint tracking + * perspective. (For example, string concatenation, where one of the operands + * is tainted.) + * + * Each use of the taint tracking library must define its own unique extension + * of this abstract class. A configuration defines a set of relevant sources + * (`isSource`) and sinks (`isSink`), and may additionally treat intermediate + * nodes as "sanitizers" (`isSanitizer`) as well as add custom taint flow steps + * (`isAdditionalTaintStep()`). + */ + abstract class Configuration extends DataFlow::Configuration { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant taint source. + * + * The smaller this predicate is, the faster `hasFlow()` will converge. + */ + // overridden to provide taint-tracking specific qldoc + abstract override predicate isSource(DataFlow::Node source); + + /** + * Holds if `sink` is a relevant taint sink. + * + * The smaller this predicate is, the faster `hasFlow()` will converge. + */ + // overridden to provide taint-tracking specific qldoc + abstract override predicate isSink(DataFlow::Node sink); + + /** Holds if the intermediate node `node` is a taint sanitizer. */ + predicate isSanitizer(DataFlow::Node node) { none() } + + final override predicate isBarrier(DataFlow::Node node) { isSanitizer(node) } + + /** + * Holds if the additional taint propagation step from `pred` to `succ` + * must be taken into account in the analysis. + */ + predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { none() } + + final override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + isAdditionalTaintStep(pred, succ) + or + taintStep(pred, succ) + } + + /** + * Holds if taint may flow from `source` to `sink` for this configuration. + */ + // overridden to provide taint-tracking specific qldoc + override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) { + super.hasFlow(source, sink) + } + } + + /** + * Holds if taint flows from `pred` to `succ` in one step. + */ + private predicate taintStep(DataFlow::Node pred, DataFlow::Node succ) { + // if x is tainted, then so is &x + succ.asExpr().(AddressExpr).getOperand() = pred.asExpr() + or + // if x is tainted, then so is *x + succ.asExpr().(StarExpr).getBase() = pred.asExpr() + or + // if an array is tainted, then so are all its elements + succ.asExpr().(IndexExpr).getBase() = pred.asExpr() + or + // if a tuple is tainted, then so are all its components + succ = DataFlow::extractTupleElement(pred, _) + or + // taint propagates through string concatenation + succ.asExpr().(AddExpr).getAnOperand() = pred.asExpr() + or + // taint propagates through slicing + succ.asExpr().(SliceExpr).getBase() = pred.asExpr() + or + // step through function model + exists(FunctionModel m, DataFlow::CallNode c, FunctionInput inp, FunctionOutput outp | + c = m.getACall() and + m.hasTaintFlow(inp, outp) and + pred = inp.getNode(c) and + succ = outp.getNode(c) + ) + } + + /** + * A model of a function specifying that the function propagates taint from + * a parameter or qualifier to a result. + */ + abstract class FunctionModel extends Function { + abstract predicate hasTaintFlow(FunctionInput input, FunctionOutput output); + } +} diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll b/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll new file mode 100644 index 00000000..c85e2631 --- /dev/null +++ b/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll @@ -0,0 +1,38 @@ +private import go +private import DataFlowPrivate + +DataFlowCallable viableImpl(DataFlowCall ma) { result = viableCallable(ma) } + +/** + * Gets a function that might be called by `call`. + */ +DataFlowCallable viableCallable(CallExpr ma) { + result = ma.getACallee() +} + +/** + * Holds if the call context `ctx` reduces the set of viable dispatch + * targets of `ma` in `c`. + */ +predicate reducedViableImplInCallContext(DataFlowCall ma, DataFlowCallable c, DataFlowCall ctx) { + none() +} + +/** + * Gets a viable dispatch target of `ma` in the context `ctx`. This is + * restricted to those `ma`s for which the context makes a difference. + */ +DataFlowCallable prunedViableImplInCallContext(DataFlowCall ma, DataFlowCall ctx) { none() } + +/** + * Holds if flow returning from `m` to `ma` might return further and if + * this path restricts the set of call sites that can be returned to. + */ +predicate reducedViableImplInReturn(DataFlowCallable m, DataFlowCall ma) { none() } + +/** + * Gets a viable dispatch target of `ma` in the context `ctx`. This is + * restricted to those `ma`s and results for which the return flow from the + * result to `ma` restricts the possible context `ctx`. + */ +DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall ma, DataFlowCall ctx) { none() } diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll b/ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll new file mode 100644 index 00000000..f797b3bc --- /dev/null +++ b/ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll @@ -0,0 +1,1864 @@ +/** + * Provides an implementation of global (interprocedural) data flow. This file + * re-exports the local (intraprocedural) data flow analysis from + * `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed + * through the `Configuration` class. This file exists in several identical + * copies, allowing queries to use multiple `Configuration` classes that depend + * on each other without introducing mutual recursion among those configurations. + */ + +private import DataFlowImplCommon +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public + +/** + * A configuration of interprocedural data flow analysis. This defines + * sources, sinks, and any other configurable aspect of the analysis. Each + * use of the global data flow library must define its own unique extension + * of this abstract class. To create a configuration, extend this class with + * a subclass whose characteristic predicate is a unique singleton string. + * For example, write + * + * ``` + * class MyAnalysisConfiguration extends DataFlow::Configuration { + * MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" } + * // Override `isSource` and `isSink`. + * // Optionally override `isBarrier`. + * // Optionally override `isAdditionalFlowStep`. + * } + * ``` + * Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and + * the edges are those data-flow steps that preserve the value of the node + * along with any additional edges defined by `isAdditionalFlowStep`. + * Specifying nodes in `isBarrier` will remove those nodes from the graph, and + * specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going + * and/or out-going edges from those nodes, respectively. + * + * Then, to query whether there is flow between some `source` and `sink`, + * write + * + * ``` + * exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink)) + * ``` + * + * Multiple configurations can coexist, but two classes extending + * `DataFlow::Configuration` should never depend on each other. One of them + * should instead depend on a `DataFlow2::Configuration`, a + * `DataFlow3::Configuration`, or a `DataFlow4::Configuration`. + */ +abstract class Configuration extends string { + bindingset[this] + Configuration() { any() } + + /** + * Holds if `source` is a relevant data flow source. + */ + abstract predicate isSource(Node source); + + /** + * Holds if `sink` is a relevant data flow sink. + */ + abstract predicate isSink(Node sink); + + /** + * Holds if data flow through `node` is prohibited. This completely removes + * `node` from the data flow graph. + */ + predicate isBarrier(Node node) { none() } + + /** DEPRECATED: override `isBarrierIn` and `isBarrierOut` instead. */ + deprecated predicate isBarrierEdge(Node node1, Node node2) { none() } + + /** Holds if data flow into `node` is prohibited. */ + predicate isBarrierIn(Node node) { none() } + + /** Holds if data flow out of `node` is prohibited. */ + predicate isBarrierOut(Node node) { none() } + + /** Holds if data flow through nodes guarded by `guard` is prohibited. */ + predicate isBarrierGuard(BarrierGuard guard) { none() } + + /** + * Holds if the additional flow step from `node1` to `node2` must be taken + * into account in the analysis. + */ + predicate isAdditionalFlowStep(Node node1, Node node2) { none() } + + /** + * Gets the virtual dispatch branching limit when calculating field flow. + * This can be overridden to a smaller value to improve performance (a + * value of 0 disables field flow), or a larger value to get more results. + */ + int fieldFlowBranchLimit() { result = 2 } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + */ + predicate hasFlow(Node source, Node sink) { flowsTo(source, sink, this) } + + /** + * Holds if data may flow from `source` to `sink` for this configuration. + * + * The corresponding paths are generated from the end-points and the graph + * included in the module `PathGraph`. + */ + predicate hasFlowPath(PathNode source, PathNode sink) { flowsTo(source, sink, _, _, this) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowTo(Node sink) { hasFlow(_, sink) } + + /** + * Holds if data may flow from some source to `sink` for this configuration. + */ + predicate hasFlowToExpr(DataFlowExpr sink) { hasFlowTo(exprNode(sink)) } + + /** DEPRECATED: use `hasFlow` instead. */ + deprecated predicate hasFlowForward(Node source, Node sink) { hasFlow(source, sink) } + + /** DEPRECATED: use `hasFlow` instead. */ + deprecated predicate hasFlowBackward(Node source, Node sink) { hasFlow(source, sink) } +} + +private predicate inBarrier(Node node, Configuration config) { + config.isBarrierIn(node) and + config.isSource(node) +} + +private predicate outBarrier(Node node, Configuration config) { + config.isBarrierOut(node) and + config.isSink(node) +} + +private predicate fullBarrier(Node node, Configuration config) { + config.isBarrier(node) + or + config.isBarrierIn(node) and + not config.isSource(node) + or + config.isBarrierOut(node) and + not config.isSink(node) + or + exists(BarrierGuard g | + config.isBarrierGuard(g) and + node = g.getAGuardedNode() + ) +} + +private class AdditionalFlowStepSource extends Node { + AdditionalFlowStepSource() { any(Configuration c).isAdditionalFlowStep(this, _) } +} + +pragma[noinline] +private predicate isAdditionalFlowStep( + AdditionalFlowStepSource node1, Node node2, DataFlowCallable callable1, Configuration config +) { + config.isAdditionalFlowStep(node1, node2) and + callable1 = node1.getEnclosingCallable() +} + +/** + * Holds if data can flow in one local step from `node1` to `node2`. + */ +private predicate localFlowStep(Node node1, Node node2, Configuration config) { + localFlowStep(node1, node2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) +} + +/** + * Holds if the additional step from `node1` to `node2` does not jump between callables. + */ +private predicate additionalLocalFlowStep(Node node1, Node node2, Configuration config) { + isAdditionalFlowStep(node1, node2, node2.getEnclosingCallable(), config) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) +} + +/** + * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. + */ +private predicate jumpStep(Node node1, Node node2, Configuration config) { + jumpStep(node1, node2) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) +} + +/** + * Holds if the additional step from `node1` to `node2` jumps between callables. + */ +private predicate additionalJumpStep(Node node1, Node node2, Configuration config) { + exists(DataFlowCallable callable1 | + isAdditionalFlowStep(node1, node2, callable1, config) and + node2.getEnclosingCallable() != callable1 and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + not fullBarrier(node1, config) and + not fullBarrier(node2, config) + ) +} + +/** + * Holds if field flow should be used for the given configuration. + */ +private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 } + +pragma[noinline] +private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKind kind) { + viableImpl(call) = result.getCallable() and + kind = result.getKind() +} + +/** + * Holds if `node` is reachable from a source in the given configuration + * ignoring call contexts. + */ +private predicate nodeCandFwd1(Node node, boolean stored, Configuration config) { + not fullBarrier(node, config) and + ( + config.isSource(node) and stored = false + or + exists(Node mid | + nodeCandFwd1(mid, stored, config) and + localFlowStep(mid, node, config) + ) + or + exists(Node mid | + nodeCandFwd1(mid, stored, config) and + additionalLocalFlowStep(mid, node, config) and + stored = false + ) + or + exists(Node mid | + nodeCandFwd1(mid, stored, config) and + jumpStep(mid, node, config) + ) + or + exists(Node mid | + nodeCandFwd1(mid, stored, config) and + additionalJumpStep(mid, node, config) and + stored = false + ) + or + // store + exists(Node mid | + useFieldFlow(config) and + nodeCandFwd1(mid, _, config) and + store(mid, _, node) and + stored = true and + not outBarrier(mid, config) + ) + or + // read + exists(Node mid, Content f | + nodeCandFwd1(mid, true, config) and + read(mid, f, node) and + storeCandFwd1(f, unbind(config)) and + (stored = false or stored = true) and + not inBarrier(node, config) + ) + or + // flow into a callable + exists(Node arg | + nodeCandFwd1(arg, stored, config) and + viableParamArg(_, node, arg) + ) + or + // flow out of an argument + exists(PostUpdateNode mid, ParameterNode p | + nodeCandFwd1(mid, stored, config) and + parameterValueFlowsToUpdate(p, mid) and + viableParamArg(_, p, node.(PostUpdateNode).getPreUpdateNode()) + ) + or + // flow out of a callable + exists(DataFlowCall call, ReturnNode ret, ReturnKind kind | + nodeCandFwd1(ret, stored, config) and + getReturnPosition(ret) = viableReturnPos(call, kind) and + node = getAnOutNode(call, kind) + ) + ) +} + +/** + * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. + */ +private predicate storeCandFwd1(Content f, Configuration config) { + exists(Node mid, Node node | + not fullBarrier(node, config) and + useFieldFlow(config) and + nodeCandFwd1(mid, _, config) and + store(mid, f, node) + ) +} + +bindingset[result, b] +private boolean unbindBool(boolean b) { result != b.booleanNot() } + +/** + * Holds if `node` is part of a path from a source to a sink in the given + * configuration ignoring call contexts. + */ +pragma[nomagic] +private predicate nodeCand1(Node node, boolean stored, Configuration config) { + nodeCandFwd1(node, false, config) and + config.isSink(node) and + stored = false + or + nodeCandFwd1(node, unbindBool(stored), unbind(config)) and + ( + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, stored, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, stored, config) and + stored = false + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, stored, config) + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, stored, config) and + stored = false + ) + or + // store + exists(Node mid, Content f | + store(node, f, mid) and + readCand1(f, unbind(config)) and + nodeCand1(mid, true, config) and + (stored = false or stored = true) + ) + or + // read + exists(Node mid, Content f | + read(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, _, config) and + stored = true + ) + or + // flow into a callable + exists(Node param | + viableParamArg(_, param, node) and + nodeCand1(param, stored, config) + ) + or + // flow out of an argument + exists(PostUpdateNode mid, ParameterNode p | + parameterValueFlowsToUpdate(p, node) and + viableParamArg(_, p, mid.getPreUpdateNode()) and + nodeCand1(mid, stored, config) + ) + or + // flow out of a callable + exists(DataFlowCall call, ReturnKind kind, OutNode out | + nodeCand1(out, stored, config) and + getReturnPosition(node) = viableReturnPos(call, kind) and + out = getAnOutNode(call, kind) + ) + ) +} + +/** + * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. + */ +private predicate readCand1(Content f, Configuration config) { + exists(Node mid, Node node | + useFieldFlow(config) and + nodeCandFwd1(node, true, unbind(config)) and + read(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, _, config) + ) +} + +private predicate throughFlowNodeCand(Node node, Configuration config) { + nodeCand1(node, false, config) and + not fullBarrier(node, config) and + not inBarrier(node, config) and + not outBarrier(node, config) +} + +/** + * Holds if there is a path from `p` to `node` in the same callable that is + * part of a path from a source to a sink taking simple call contexts into + * consideration. This is restricted to paths that do not necessarily + * preserve the value of `p` by making use of at least one additional step + * from the configuration. + */ +pragma[nomagic] +private predicate simpleParameterFlow( + ParameterNode p, Node node, DataFlowType t, Configuration config +) { + throughFlowNodeCand(node, config) and + p = node and + t = getErasedRepr(node.getType()) and + exists(ReturnNode ret, ReturnKind kind | + returnNodeGetEnclosingCallable(ret) = p.getEnclosingCallable() and + kind = ret.getKind() and + not parameterValueFlowsThrough(p, kind, _) + ) + or + throughFlowNodeCand(node, unbind(config)) and + exists(Node mid | + simpleParameterFlow(p, mid, t, config) and + localFlowStep(mid, node, config) and + compatibleTypes(t, node.getType()) + ) + or + throughFlowNodeCand(node, unbind(config)) and + exists(Node mid | + simpleParameterFlow(p, mid, _, config) and + additionalLocalFlowStep(mid, node, config) and + t = getErasedRepr(node.getType()) + ) + or + throughFlowNodeCand(node, unbind(config)) and + exists(Node mid | + simpleParameterFlow(p, mid, t, config) and + localStoreReadStep(mid, node) and + compatibleTypes(t, node.getType()) + ) + or + // value flow through a callable + throughFlowNodeCand(node, unbind(config)) and + exists(Node arg | + simpleParameterFlow(p, arg, t, config) and + argumentValueFlowsThrough(arg, node, _) and + compatibleTypes(t, node.getType()) + ) + or + // flow through a callable + throughFlowNodeCand(node, unbind(config)) and + exists(Node arg | + simpleParameterFlow(p, arg, _, config) and + simpleArgumentFlowsThrough(arg, node, t, config) + ) +} + +pragma[noinline] +private predicate simpleArgumentFlowsThrough0( + DataFlowCall call, ArgumentNode arg, ReturnKind kind, DataFlowType t, Configuration config +) { + nodeCand1(arg, false, unbind(config)) and + not outBarrier(arg, config) and + exists(ParameterNode p, ReturnNode ret | + simpleParameterFlow(p, ret, t, config) and + kind = ret.getKind() and + viableParamArg(call, p, arg) + ) +} + +/** + * Holds if data can flow from `arg` to `out` through a call, taking simple + * call contexts into consideration, and that this is part of a path from a + * source to a sink. This is restricted to paths through calls that do not + * necessarily preserve the value of `arg` by making use of at least one + * additional step from the configuration. + */ +private predicate simpleArgumentFlowsThrough( + ArgumentNode arg, Node out, DataFlowType t, Configuration config +) { + exists(DataFlowCall call, ReturnKind kind | + nodeCand1(out, false, unbind(config)) and + not inBarrier(out, config) and + simpleArgumentFlowsThrough0(call, arg, kind, t, config) and + out = getAnOutNode(call, kind) + ) +} + +/** + * Holds if data can flow from `node1` to `node2` in one local step or a step + * through a callable. + */ +pragma[noinline] +private predicate localFlowStepOrFlowThroughCallable(Node node1, Node node2, Configuration config) { + nodeCand1(node1, _, config) and + localFlowStep(node1, node2, config) + or + nodeCand1(node1, _, config) and + argumentValueFlowsThrough(node1, node2, _) +} + +/** + * Holds if data can flow from `node1` to `node2` in one local step or a step + * through a callable, in both cases using an additional flow step from the + * configuration. + */ +pragma[noinline] +private predicate additionalLocalFlowStepOrFlowThroughCallable( + Node node1, Node node2, Configuration config +) { + nodeCand1(node1, _, config) and + additionalLocalFlowStep(node1, node2, config) + or + simpleArgumentFlowsThrough(node1, node2, _, config) +} + +/** + * Holds if data can flow out of a callable from `node1` to `node2`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. + */ +private predicate flowOutOfCallable(Node node1, Node node2, Configuration config) { + nodeCand1(node1, _, unbind(config)) and + nodeCand1(node2, _, config) and + not outBarrier(node1, config) and + not inBarrier(node2, config) and + ( + // flow out of an argument + exists(ParameterNode p | + parameterValueFlowsToUpdate(p, node1) and + viableParamArg(_, p, node2.(PostUpdateNode).getPreUpdateNode()) + ) + or + // flow out of a callable + exists(DataFlowCall call, ReturnKind kind | + getReturnPosition(node1) = viableReturnPos(call, kind) and + node2 = getAnOutNode(call, kind) + ) + ) +} + +/** + * Holds if data can flow into a callable and that this step is part of a + * path from a source to a sink. + */ +private predicate flowIntoCallable(Node node1, Node node2, Configuration config) { + viableParamArg(_, node2, node1) and + nodeCand1(node1, _, unbind(config)) and + nodeCand1(node2, _, config) and + not outBarrier(node1, config) and + not inBarrier(node2, config) +} + +/** + * Gets the amount of forward branching on the origin of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ +private int branch(Node n1, Configuration conf) { + result = strictcount(Node n | flowOutOfCallable(n1, n, conf) or flowIntoCallable(n1, n, conf)) +} + +/** + * Gets the amount of backward branching on the target of a cross-call path + * edge in the graph of paths between sources and sinks that ignores call + * contexts. + */ +private int join(Node n2, Configuration conf) { + result = strictcount(Node n | flowOutOfCallable(n, n2, conf) or flowIntoCallable(n, n2, conf)) +} + +/** + * Holds if data can flow out of a callable from `node1` to `node2`, either + * through a `ReturnNode` or through an argument that has been mutated, and + * that this step is part of a path from a source to a sink. The + * `allowsFieldFlow` flag indicates whether the branching is within the limit + * specified by the configuration. + */ +private predicate flowOutOfCallable( + Node node1, Node node2, boolean allowsFieldFlow, Configuration config +) { + flowOutOfCallable(node1, node2, config) and + exists(int b, int j | + b = branch(node1, config) and + j = join(node2, config) and + if b.minimum(j) <= config.fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) +} + +/** + * Holds if data can flow into a callable and that this step is part of a + * path from a source to a sink. The `allowsFieldFlow` flag indicates whether + * the branching is within the limit specified by the configuration. + */ +private predicate flowIntoCallable( + Node node1, Node node2, boolean allowsFieldFlow, Configuration config +) { + flowIntoCallable(node1, node2, config) and + exists(int b, int j | + b = branch(node1, config) and + j = join(node2, config) and + if b.minimum(j) <= config.fieldFlowBranchLimit() + then allowsFieldFlow = true + else allowsFieldFlow = false + ) +} + +/** + * Holds if `node` is part of a path from a source to a sink in the given + * configuration taking simple call contexts into consideration. + */ +private predicate nodeCandFwd2(Node node, boolean fromArg, boolean stored, Configuration config) { + nodeCand1(node, false, config) and + config.isSource(node) and + fromArg = false and + stored = false + or + nodeCand1(node, unbindBool(stored), unbind(config)) and + ( + exists(Node mid | + nodeCandFwd2(mid, fromArg, stored, config) and + localFlowStepOrFlowThroughCallable(mid, node, config) + ) + or + exists(Node mid | + nodeCandFwd2(mid, fromArg, stored, config) and + additionalLocalFlowStepOrFlowThroughCallable(mid, node, config) and + stored = false + ) + or + exists(Node mid | + nodeCandFwd2(mid, _, stored, config) and + jumpStep(mid, node, config) and + fromArg = false + ) + or + exists(Node mid | + nodeCandFwd2(mid, _, stored, config) and + additionalJumpStep(mid, node, config) and + fromArg = false and + stored = false + ) + or + // store + exists(Node mid, Content f | + nodeCandFwd2(mid, fromArg, _, config) and + store(mid, f, node) and + readCand1(f, unbind(config)) and + stored = true + ) + or + // read + exists(Node mid, Content f | + nodeCandFwd2(mid, fromArg, true, config) and + read(mid, f, node) and + storeCandFwd2(f, unbind(config)) and + (stored = false or stored = true) + ) + or + exists(Node mid, boolean allowsFieldFlow | + nodeCandFwd2(mid, _, stored, config) and + flowIntoCallable(mid, node, allowsFieldFlow, config) and + fromArg = true and + (stored = false or allowsFieldFlow = true) + ) + or + exists(Node mid, boolean allowsFieldFlow | + nodeCandFwd2(mid, false, stored, config) and + flowOutOfCallable(mid, node, allowsFieldFlow, config) and + fromArg = false and + (stored = false or allowsFieldFlow = true) + ) + ) +} + +/** + * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd2`. + */ +private predicate storeCandFwd2(Content f, Configuration config) { + exists(Node mid, Node node | + useFieldFlow(config) and + nodeCand1(node, true, unbind(config)) and + nodeCandFwd2(mid, _, _, config) and + store(mid, f, node) and + readCand1(f, unbind(config)) + ) +} + +/** + * Holds if `node` is part of a path from a source to a sink in the given + * configuration taking simple call contexts into consideration. + */ +private predicate nodeCand2(Node node, boolean toReturn, boolean stored, Configuration config) { + nodeCandFwd2(node, _, false, config) and + config.isSink(node) and + toReturn = false and + stored = false + or + nodeCandFwd2(node, _, unbindBool(stored), unbind(config)) and + ( + exists(Node mid | + localFlowStepOrFlowThroughCallable(node, mid, config) and + nodeCand2(mid, toReturn, stored, config) + ) + or + exists(Node mid | + additionalLocalFlowStepOrFlowThroughCallable(node, mid, config) and + nodeCand2(mid, toReturn, stored, config) and + stored = false + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand2(mid, _, stored, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand2(mid, _, stored, config) and + toReturn = false and + stored = false + ) + or + // store + exists(Node mid, Content f | + store(node, f, mid) and + readCand2(f, unbind(config)) and + nodeCand2(mid, toReturn, true, config) and + (stored = false or stored = true) + ) + or + // read + exists(Node mid, Content f | + read(node, f, mid) and + storeCandFwd2(f, unbind(config)) and + nodeCand2(mid, toReturn, _, config) and + stored = true + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowIntoCallable(node, mid, allowsFieldFlow, config) and + nodeCand2(mid, false, stored, config) and + toReturn = false and + (stored = false or allowsFieldFlow = true) + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowOutOfCallable(node, mid, allowsFieldFlow, config) and + nodeCand2(mid, _, stored, config) and + toReturn = true and + (stored = false or allowsFieldFlow = true) + ) + ) +} + +/** + * Holds if `f` is the target of a read in the flow covered by `nodeCand2`. + */ +private predicate readCand2(Content f, Configuration config) { + exists(Node mid, Node node | + useFieldFlow(config) and + nodeCandFwd2(node, _, true, unbind(config)) and + read(node, f, mid) and + storeCandFwd2(f, unbind(config)) and + nodeCand2(mid, _, _, config) + ) +} + +pragma[nomagic] +private predicate storeCand(Content f, Configuration conf) { + exists(Node n1, Node n2 | + store(n1, f, n2) and + nodeCand2(n1, _, _, conf) and + nodeCand2(n2, _, _, unbind(conf)) + ) +} + +private predicate readCand(Content f, Configuration conf) { readCand2(f, conf) } + +/** + * Holds if `f` is the target of both a store and a read in the path graph + * covered by `nodeCand2`. + */ +pragma[noinline] +private predicate readStoreCand(Content f, Configuration conf) { + storeCand(f, conf) and + readCand(f, conf) +} + +private predicate nodeCand(Node node, Configuration config) { nodeCand2(node, _, _, config) } + +/** + * Holds if `node` can be the first node in a maximal subsequence of local + * flow steps in a dataflow path. + */ +private predicate localFlowEntry(Node node, Configuration config) { + nodeCand(node, config) and + ( + config.isSource(node) or + jumpStep(_, node, config) or + additionalJumpStep(_, node, config) or + node instanceof ParameterNode or + node instanceof OutNode or + node instanceof PostUpdateNode or + read(_, _, node) or + node instanceof CastNode + ) +} + +/** + * Holds if `node` can be the last node in a maximal subsequence of local + * flow steps in a dataflow path. + */ +private predicate localFlowExit(Node node, Configuration config) { + exists(Node next | nodeCand(next, config) | + jumpStep(node, next, config) or + additionalJumpStep(node, next, config) or + flowIntoCallable(node, next, config) or + flowOutOfCallable(node, next, config) or + simpleArgumentFlowsThrough(node, next, _, config) or + argumentValueFlowsThrough(node, next, _) or + store(node, _, next) or + read(node, _, next) + ) + or + node instanceof CastNode + or + config.isSink(node) +} + +/** + * Holds if the local path from `node1` to `node2` is a prefix of a maximal + * subsequence of local flow steps in a dataflow path. + * + * This is the transitive closure of `[additional]localFlowStep` beginning + * at `localFlowEntry`. + */ +pragma[nomagic] +private predicate localFlowStepPlus( + Node node1, Node node2, boolean preservesValue, Configuration config +) { + localFlowEntry(node1, config) and + ( + localFlowStep(node1, node2, config) and preservesValue = true + or + additionalLocalFlowStep(node1, node2, config) and preservesValue = false + ) and + node1 != node2 and + nodeCand(node2, unbind(config)) + or + exists(Node mid | + localFlowStepPlus(node1, mid, preservesValue, config) and + localFlowStep(mid, node2, config) and + not mid instanceof CastNode and + nodeCand(node2, unbind(config)) + ) + or + exists(Node mid | + localFlowStepPlus(node1, mid, _, config) and + additionalLocalFlowStep(mid, node2, config) and + not mid instanceof CastNode and + preservesValue = false and + nodeCand(node2, unbind(config)) + ) +} + +/** + * Holds if `node1` can step to `node2` in one or more local steps and this + * path can occur as a maximal subsequence of local steps in a dataflow path. + */ +pragma[noinline] +private predicate localFlowBigStep( + Node node1, Node node2, boolean preservesValue, Configuration config +) { + localFlowStepPlus(node1, node2, preservesValue, config) and + localFlowExit(node2, config) +} + +private newtype TAccessPathFront = + TFrontNil(DataFlowType t) or + TFrontHead(Content f) + +/** + * The front of an `AccessPath`. This is either a head or a nil. + */ +private class AccessPathFront extends TAccessPathFront { + string toString() { + exists(DataFlowType t | this = TFrontNil(t) | result = ppReprType(t)) + or + exists(Content f | this = TFrontHead(f) | result = f.toString()) + } + + DataFlowType getType() { + this = TFrontNil(result) + or + exists(Content head | this = TFrontHead(head) | result = head.getContainerType()) + } + + predicate headUsesContent(Content f) { this = TFrontHead(f) } +} + +private class AccessPathFrontNil extends AccessPathFront, TFrontNil { } + +/** + * A `Node` at which a cast can occur such that the type should be checked. + */ +private class CastingNode extends Node { + CastingNode() { + this instanceof ParameterNode or + this instanceof CastNode or + this instanceof OutNode or + this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode + } +} + +/** + * Holds if data can flow from a source to `node` with the given `apf`. + */ +private predicate flowCandFwd(Node node, boolean fromArg, AccessPathFront apf, Configuration config) { + flowCandFwd0(node, fromArg, apf, config) and + if node instanceof CastingNode then compatibleTypes(node.getType(), apf.getType()) else any() +} + +/** + * A node that requires an empty access path and should have its tracked type + * (re-)computed. This is either a source or a node reached through an + * additional step. + */ +private class AccessPathFrontNilNode extends Node { + AccessPathFrontNilNode() { + nodeCand(this, _) and + ( + any(Configuration c).isSource(this) + or + localFlowBigStep(_, this, false, _) + or + additionalJumpStep(_, this, _) + ) + } + + pragma[noinline] + private DataFlowType getErasedReprType() { result = getErasedRepr(this.getType()) } + + /** Gets the `nil` path front for this node. */ + AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedReprType()) } +} + +private predicate flowCandFwd0(Node node, boolean fromArg, AccessPathFront apf, Configuration config) { + nodeCand2(node, _, false, config) and + config.isSource(node) and + fromArg = false and + apf = node.(AccessPathFrontNilNode).getApf() + or + nodeCand(node, unbind(config)) and + ( + exists(Node mid | + flowCandFwd(mid, fromArg, apf, config) and + localFlowBigStep(mid, node, true, config) + ) + or + exists(Node mid, AccessPathFrontNil nil | + flowCandFwd(mid, fromArg, nil, config) and + localFlowBigStep(mid, node, false, config) and + apf = node.(AccessPathFrontNilNode).getApf() + ) + or + exists(Node mid | + flowCandFwd(mid, _, apf, config) and + jumpStep(mid, node, config) and + fromArg = false + ) + or + exists(Node mid, AccessPathFrontNil nil | + flowCandFwd(mid, _, nil, config) and + additionalJumpStep(mid, node, config) and + fromArg = false and + apf = node.(AccessPathFrontNilNode).getApf() + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowCandFwd(mid, _, apf, config) and + flowIntoCallable(mid, node, allowsFieldFlow, config) and + fromArg = true and + (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowCandFwd(mid, false, apf, config) and + flowOutOfCallable(mid, node, allowsFieldFlow, config) and + fromArg = false and + (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) + ) + or + exists(Node mid | + flowCandFwd(mid, fromArg, apf, config) and + argumentValueFlowsThrough(mid, node, _) + ) + or + exists(Node mid, AccessPathFrontNil nil, DataFlowType t | + flowCandFwd(mid, fromArg, nil, config) and + simpleArgumentFlowsThrough(mid, node, t, config) and + apf = TFrontNil(t) + ) + ) + or + exists(Node mid, Content f | + flowCandFwd(mid, fromArg, _, config) and + store(mid, f, node) and + nodeCand(node, unbind(config)) and + apf.headUsesContent(f) + ) + or + exists(Node mid, Content f, AccessPathFront apf0 | + flowCandFwd(mid, fromArg, apf0, config) and + read(mid, f, node) and + nodeCand(node, config) and + apf0.headUsesContent(f) and + consCandFwd(f, apf, unbind(config)) + ) +} + +private predicate consCandFwd(Content f, AccessPathFront apf, Configuration config) { + exists(Node mid, Node n | + flowCandFwd(mid, _, apf, config) and + store(mid, f, n) and + nodeCand(n, unbind(config)) and + readStoreCand(f, unbind(config)) and + compatibleTypes(apf.getType(), f.getType()) + ) +} + +/** + * Holds if data can flow from a source to `node` with the given `apf` and + * from there flow to a sink. + */ +private predicate flowCand(Node node, boolean toReturn, AccessPathFront apf, Configuration config) { + flowCand0(node, toReturn, apf, config) and + flowCandFwd(node, _, apf, config) +} + +private predicate flowCand0(Node node, boolean toReturn, AccessPathFront apf, Configuration config) { + flowCandFwd(node, _, apf, config) and + config.isSink(node) and + toReturn = false and + apf instanceof AccessPathFrontNil + or + exists(Node mid | + localFlowBigStep(node, mid, true, config) and + flowCand(mid, toReturn, apf, config) + ) + or + exists(Node mid, AccessPathFrontNil nil | + flowCandFwd(node, _, apf, config) and + localFlowBigStep(node, mid, false, config) and + flowCand(mid, toReturn, nil, config) and + apf instanceof AccessPathFrontNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + flowCand(mid, _, apf, config) and + toReturn = false + ) + or + exists(Node mid, AccessPathFrontNil nil | + flowCandFwd(node, _, apf, config) and + additionalJumpStep(node, mid, config) and + flowCand(mid, _, nil, config) and + toReturn = false and + apf instanceof AccessPathFrontNil + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowIntoCallable(node, mid, allowsFieldFlow, config) and + flowCand(mid, false, apf, config) and + toReturn = false and + (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flowCand(mid, _, apf, config) and + toReturn = true and + (apf instanceof AccessPathFrontNil or allowsFieldFlow = true) + ) + or + exists(Node mid | + argumentValueFlowsThrough(node, mid, _) and + flowCand(mid, toReturn, apf, config) + ) + or + exists(Node mid, AccessPathFrontNil nil | + simpleArgumentFlowsThrough(node, mid, _, config) and + flowCand(mid, toReturn, nil, config) and + apf instanceof AccessPathFrontNil and + flowCandFwd(node, _, apf, config) + ) + or + exists(Content f, AccessPathFront apf0 | + flowCandStore(node, f, toReturn, apf0, config) and + apf0.headUsesContent(f) and + consCand(f, apf, unbind(config)) + ) + or + exists(Content f, AccessPathFront apf0 | + flowCandRead(node, f, toReturn, apf0, config) and + consCandFwd(f, apf0, unbind(config)) and + apf.headUsesContent(f) + ) +} + +pragma[nomagic] +private predicate flowCandRead( + Node node, Content f, boolean toReturn, AccessPathFront apf0, Configuration config +) { + exists(Node mid | + read(node, f, mid) and + flowCand(mid, toReturn, apf0, config) + ) +} + +private predicate flowCandStore( + Node node, Content f, boolean toReturn, AccessPathFront apf0, Configuration config +) { + exists(Node mid | + store(node, f, mid) and + flowCand(mid, toReturn, apf0, config) + ) +} + +private predicate consCand(Content f, AccessPathFront apf, Configuration config) { + consCandFwd(f, apf, config) and + exists(Node n, AccessPathFront apf0 | + flowCandFwd(n, _, apf0, config) and + apf0.headUsesContent(f) and + flowCandRead(n, f, _, apf, config) + ) +} + +private newtype TAccessPath = + TNil(DataFlowType t) or + TCons(Content f, int len) { len in [1 .. 5] } + +/** + * Conceptually a list of `Content`s followed by a `Type`, but only the first + * element of the list and its length are tracked. If data flows from a source to + * a given node with a given `AccessPath`, this indicates the sequence of + * dereference operations needed to get from the value in the node to the + * tracked object. The final type indicates the type of the tracked object. + */ +private class AccessPath extends TAccessPath { + abstract string toString(); + + Content getHead() { this = TCons(result, _) } + + int len() { + this = TNil(_) and result = 0 + or + this = TCons(_, result) + } + + DataFlowType getType() { + this = TNil(result) + or + exists(Content head | this = TCons(head, _) | result = head.getContainerType()) + } + + abstract AccessPathFront getFront(); +} + +private class AccessPathNil extends AccessPath, TNil { + override string toString() { exists(DataFlowType t | this = TNil(t) | result = ppReprType(t)) } + + override AccessPathFront getFront() { + exists(DataFlowType t | this = TNil(t) | result = TFrontNil(t)) + } +} + +private class AccessPathCons extends AccessPath, TCons { + override string toString() { + exists(Content f, int len | this = TCons(f, len) | + result = f.toString() + ", ... (" + len.toString() + ")" + ) + } + + override AccessPathFront getFront() { + exists(Content f | this = TCons(f, _) | result = TFrontHead(f)) + } +} + +/** Holds if `ap0` corresponds to the cons of `f` and `ap`. */ +private predicate pop(AccessPath ap0, Content f, AccessPath ap) { + ap0.getFront().headUsesContent(f) and + consCand(f, ap.getFront(), _) and + ap0.len() = 1 + ap.len() +} + +/** Holds if `ap0` corresponds to the cons of `f` and `ap` and `apf` is the front of `ap`. */ +pragma[noinline] +private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, AccessPath ap) { + pop(ap0, f, ap) and apf = ap.getFront() +} + +/** Holds if `ap` corresponds to the cons of `f` and `ap0`. */ +private predicate push(AccessPath ap0, Content f, AccessPath ap) { pop(ap, f, ap0) } + +/** + * A node that requires an empty access path and should have its tracked type + * (re-)computed. This is either a source or a node reached through an + * additional step. + */ +private class AccessPathNilNode extends Node { + AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } + + pragma[noinline] + private DataFlowType getErasedReprType() { result = getErasedRepr(this.getType()) } + + /** Gets the `nil` path for this node. */ + AccessPathNil getAp() { result = TNil(this.getErasedReprType()) } +} + +/** + * Holds if data can flow from a source to `node` with the given `ap`. + */ +private predicate flowFwd( + Node node, boolean fromArg, AccessPathFront apf, AccessPath ap, Configuration config +) { + flowFwd0(node, fromArg, apf, ap, config) and + flowCand(node, _, apf, config) +} + +private predicate flowFwd0( + Node node, boolean fromArg, AccessPathFront apf, AccessPath ap, Configuration config +) { + flowCand(node, _, _, config) and + config.isSource(node) and + fromArg = false and + ap = node.(AccessPathNilNode).getAp() and + apf = ap.(AccessPathNil).getFront() + or + flowCand(node, _, _, unbind(config)) and + ( + exists(Node mid | + flowFwd(mid, fromArg, apf, ap, config) and + localFlowBigStep(mid, node, true, config) + ) + or + exists(Node mid, AccessPathNil nil | + flowFwd(mid, fromArg, _, nil, config) and + localFlowBigStep(mid, node, false, config) and + ap = node.(AccessPathNilNode).getAp() and + apf = ap.(AccessPathNil).getFront() + ) + or + exists(Node mid | + flowFwd(mid, _, apf, ap, config) and + jumpStep(mid, node, config) and + fromArg = false + ) + or + exists(Node mid, AccessPathNil nil | + flowFwd(mid, _, _, nil, config) and + additionalJumpStep(mid, node, config) and + fromArg = false and + ap = node.(AccessPathNilNode).getAp() and + apf = ap.(AccessPathNil).getFront() + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowFwd(mid, _, apf, ap, config) and + flowIntoCallable(mid, node, allowsFieldFlow, config) and + fromArg = true and + (ap instanceof AccessPathNil or allowsFieldFlow = true) + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowFwd(mid, false, apf, ap, config) and + flowOutOfCallable(mid, node, allowsFieldFlow, config) and + fromArg = false and + (ap instanceof AccessPathNil or allowsFieldFlow = true) + ) + or + exists(Node mid | + flowFwd(mid, fromArg, apf, ap, config) and + argumentValueFlowsThrough(mid, node, _) + ) + or + exists(Node mid, AccessPathNil nil, DataFlowType t | + flowFwd(mid, fromArg, _, nil, config) and + simpleArgumentFlowsThrough(mid, node, t, config) and + ap = TNil(t) and + apf = ap.(AccessPathNil).getFront() + ) + ) + or + exists(Content f, AccessPath ap0 | + flowFwdStore(node, f, ap0, apf, fromArg, config) and + push(ap0, f, ap) + ) + or + exists(Content f, AccessPath ap0 | + flowFwdRead(node, f, ap0, fromArg, config) and + popWithFront(ap0, f, apf, ap) + ) +} + +pragma[nomagic] +private predicate flowFwdStore( + Node node, Content f, AccessPath ap0, AccessPathFront apf, boolean fromArg, Configuration config +) { + exists(Node mid, AccessPathFront apf0 | + flowFwd(mid, fromArg, apf0, ap0, config) and + flowFwdStoreAux(mid, f, node, apf0, apf, config) + ) +} + +private predicate flowFwdStoreAux( + Node mid, Content f, Node node, AccessPathFront apf0, AccessPathFront apf, Configuration config +) { + store(mid, f, node) and + consCand(f, apf0, config) and + apf.headUsesContent(f) and + flowCand(node, _, apf, unbind(config)) +} + +pragma[nomagic] +private predicate flowFwdRead( + Node node, Content f, AccessPath ap0, boolean fromArg, Configuration config +) { + exists(Node mid, AccessPathFront apf0 | + flowFwd(mid, fromArg, apf0, ap0, config) and + read(mid, f, node) and + apf0.headUsesContent(f) and + flowCand(node, _, _, unbind(config)) + ) +} + +/** + * Holds if data can flow from a source to `node` with the given `ap` and + * from there flow to a sink. + */ +private predicate flow(Node node, boolean toReturn, AccessPath ap, Configuration config) { + flow0(node, toReturn, ap, config) and + flowFwd(node, _, _, ap, config) +} + +private predicate flow0(Node node, boolean toReturn, AccessPath ap, Configuration config) { + flowFwd(node, _, _, ap, config) and + config.isSink(node) and + toReturn = false and + ap instanceof AccessPathNil + or + exists(Node mid | + localFlowBigStep(node, mid, true, config) and + flow(mid, toReturn, ap, config) + ) + or + exists(Node mid, AccessPathNil nil | + flowFwd(node, _, _, ap, config) and + localFlowBigStep(node, mid, false, config) and + flow(mid, toReturn, nil, config) and + ap instanceof AccessPathNil + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + flow(mid, _, ap, config) and + toReturn = false + ) + or + exists(Node mid, AccessPathNil nil | + flowFwd(node, _, _, ap, config) and + additionalJumpStep(node, mid, config) and + flow(mid, _, nil, config) and + toReturn = false and + ap instanceof AccessPathNil + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowIntoCallable(node, mid, allowsFieldFlow, config) and + flow(mid, false, ap, config) and + toReturn = false and + (ap instanceof AccessPathNil or allowsFieldFlow = true) + ) + or + exists(Node mid, boolean allowsFieldFlow | + flowOutOfCallable(node, mid, allowsFieldFlow, config) and + flow(mid, _, ap, config) and + toReturn = true and + (ap instanceof AccessPathNil or allowsFieldFlow = true) + ) + or + exists(Node mid | + argumentValueFlowsThrough(node, mid, _) and + flow(mid, toReturn, ap, config) + ) + or + exists(Node mid, AccessPathNil ap0 | + simpleArgumentFlowsThrough(node, mid, _, config) and + flow(mid, toReturn, ap0, config) and + ap instanceof AccessPathNil and + flowFwd(node, _, _, ap, config) + ) + or + exists(Content f, AccessPath ap0 | + flowStore(node, f, toReturn, ap0, config) and + pop(ap0, f, ap) + ) + or + exists(Content f, AccessPath ap0 | + flowRead(node, f, toReturn, ap0, config) and + push(ap0, f, ap) + ) +} + +pragma[nomagic] +private predicate flowStore( + Node node, Content f, boolean toReturn, AccessPath ap0, Configuration config +) { + exists(Node mid | + store(node, f, mid) and + flow(mid, toReturn, ap0, config) + ) +} + +pragma[nomagic] +private predicate flowRead( + Node node, Content f, boolean toReturn, AccessPath ap0, Configuration config +) { + exists(Node mid | + read(node, f, mid) and + flow(mid, toReturn, ap0, config) + ) +} + +bindingset[conf, result] +private Configuration unbind(Configuration conf) { result >= conf and result <= conf } + +private predicate flow(Node n, Configuration config) { flow(n, _, _, config) } + +private newtype TPathNode = + TPathNodeMid(Node node, CallContext cc, AccessPath ap, Configuration config) { + // A PathNode is introduced by a source ... + flow(node, config) and + config.isSource(node) and + cc instanceof CallContextAny and + ap = node.(AccessPathNilNode).getAp() + or + // ... or a step from an existing PathNode to another node. + exists(PathNodeMid mid | + pathStep(mid, node, cc, ap) and + config = mid.getConfiguration() and + flow(node, _, ap, unbind(config)) + ) + } or + TPathNodeSink(Node node, Configuration config) { + // The AccessPath on a sink is empty. + config.isSink(node) and + flow(node, config) + } + +/** + * A `Node` augmented with a call context (except for sinks), an access path, and a configuration. + * Only those `PathNode`s that are reachable from a source are generated. + */ +abstract class PathNode extends TPathNode { + /** Gets a textual representation of this element. */ + string toString() { result = getNode().toString() + ppAp() } + + /** + * Gets a textual representation of this element, including a textual + * representation of the call context. + */ + string toStringWithContext() { result = getNode().toString() + ppAp() + ppCtx() } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + getNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + /** Gets the underlying `Node`. */ + abstract Node getNode(); + + /** Gets the associated configuration. */ + abstract Configuration getConfiguration(); + + /** Gets a successor. */ + deprecated final PathNode getSucc() { result = this.getASuccessor() } + + /** Gets a successor of this node, if any. */ + abstract PathNode getASuccessor(); + + private string ppAp() { + this instanceof PathNodeSink and result = "" + or + exists(string s | s = this.(PathNodeMid).getAp().toString() | + if s = "" then result = "" else result = " [" + s + "]" + ) + } + + private string ppCtx() { + this instanceof PathNodeSink and result = "" + or + result = " <" + this.(PathNodeMid).getCallContext().toString() + ">" + } +} + +/** Holds if `n` can reach a sink. */ +private predicate reach(PathNode n) { n instanceof PathNodeSink or reach(n.getASuccessor()) } + +/** Holds if `n1.getSucc() = n2` and `n2` can reach a sink. */ +private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and reach(n2) } + +private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2) + +/** + * Provides the query predicates needed to include a graph in a path-problem query. + */ +module PathGraph { + /** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */ + query predicate edges(PathNode a, PathNode b) { pathSucc(a, b) } +} + +/** + * An intermediate flow graph node. This is a triple consisting of a `Node`, + * a `CallContext`, and a `Configuration`. + */ +private class PathNodeMid extends PathNode, TPathNodeMid { + Node node; + + CallContext cc; + + AccessPath ap; + + Configuration config; + + PathNodeMid() { this = TPathNodeMid(node, cc, ap, config) } + + override Node getNode() { result = node } + + CallContext getCallContext() { result = cc } + + AccessPath getAp() { result = ap } + + override Configuration getConfiguration() { result = config } + + private PathNodeMid getSuccMid() { + pathStep(this, result.getNode(), result.getCallContext(), result.getAp()) and + result.getConfiguration() = unbind(this.getConfiguration()) + } + + override PathNode getASuccessor() { + // an intermediate step to another intermediate node + result = getSuccMid() + or + // a final step to a sink via one or more local steps + localFlowStepPlus(node, result.getNode(), _, config) and + ap instanceof AccessPathNil and + result instanceof PathNodeSink and + result.getConfiguration() = unbind(this.getConfiguration()) + or + // a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges + exists(PathNodeMid mid | + mid = getSuccMid() and + mid.getNode() = result.getNode() and + mid.getAp() instanceof AccessPathNil and + result instanceof PathNodeSink and + result.getConfiguration() = unbind(mid.getConfiguration()) + ) + or + // a direct step from a source to a sink if a node is both + this instanceof PathNodeSource and + result instanceof PathNodeSink and + this.getNode() = result.getNode() and + result.getConfiguration() = unbind(this.getConfiguration()) + } +} + +/** + * A flow graph node corresponding to a source. + */ +private class PathNodeSource extends PathNodeMid { + PathNodeSource() { + getConfiguration().isSource(getNode()) and + getCallContext() instanceof CallContextAny and + getAp() instanceof AccessPathNil + } +} + +/** + * A flow graph node corresponding to a sink. This is disjoint from the + * intermediate nodes in order to uniquely correspond to a given sink by + * excluding the `CallContext`. + */ +private class PathNodeSink extends PathNode, TPathNodeSink { + Node node; + + Configuration config; + + PathNodeSink() { this = TPathNodeSink(node, config) } + + override Node getNode() { result = node } + + override Configuration getConfiguration() { result = config } + + override PathNode getASuccessor() { none() } +} + +/** + * Holds if data may flow from `mid` to `node`. The last step in or out of + * a callable is recorded by `cc`. + */ +private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, AccessPath ap) { + localFlowBigStep(mid.getNode(), node, true, mid.getConfiguration()) and + cc = mid.getCallContext() and + ap = mid.getAp() + or + localFlowBigStep(mid.getNode(), node, false, mid.getConfiguration()) and + cc = mid.getCallContext() and + mid.getAp() instanceof AccessPathNil and + ap = node.(AccessPathNilNode).getAp() + or + jumpStep(mid.getNode(), node, mid.getConfiguration()) and + cc instanceof CallContextAny and + ap = mid.getAp() + or + additionalJumpStep(mid.getNode(), node, mid.getConfiguration()) and + cc instanceof CallContextAny and + mid.getAp() instanceof AccessPathNil and + ap = node.(AccessPathNilNode).getAp() + or + contentReadStep(mid, node, ap) and cc = mid.getCallContext() + or + exists(Content f, AccessPath ap0 | contentStoreStep(mid, node, ap0, f, cc) and push(ap0, f, ap)) + or + pathOutOfArgument(mid, node, cc) and ap = mid.getAp() + or + pathIntoCallable(mid, node, _, cc, _) and ap = mid.getAp() + or + pathOutOfCallable(mid, node, cc) and ap = mid.getAp() + or + pathThroughCallable(mid, node, cc, ap) + or + valuePathThroughCallable(mid, node, cc) and ap = mid.getAp() +} + +private predicate contentReadStep(PathNodeMid mid, Node node, AccessPath ap) { + exists(Content f, AccessPath ap0 | + ap0 = mid.getAp() and + read(mid.getNode(), f, node) and + pop(ap0, f, ap) + ) +} + +pragma[noinline] +private predicate contentStoreStep( + PathNodeMid mid, Node node, AccessPath ap0, Content f, CallContext cc +) { + ap0 = mid.getAp() and + store(mid.getNode(), f, node) and + cc = mid.getCallContext() +} + +private predicate pathOutOfCallable0(PathNodeMid mid, ReturnPosition pos, CallContext innercc) { + pos = getReturnPosition(mid.getNode()) and + innercc = mid.getCallContext() and + not innercc instanceof CallContextCall +} + +pragma[noinline] +private predicate pathOutOfCallable1( + PathNodeMid mid, DataFlowCall call, ReturnKind kind, CallContext cc +) { + exists(ReturnPosition pos, DataFlowCallable c, CallContext innercc | + pathOutOfCallable0(mid, pos, innercc) and + c = pos.getCallable() and + kind = pos.getKind() and + resolveReturn(innercc, c, call) + | + if reducedViableImplInReturn(c, call) then cc = TReturn(c, call) else cc = TAnyCallContext() + ) +} + +/** + * Holds if data may flow from `mid` to `out`. The last step of this path + * is a return from a callable and is recorded by `cc`, if needed. + */ +pragma[noinline] +private predicate pathOutOfCallable(PathNodeMid mid, OutNode out, CallContext cc) { + exists(ReturnKind kind, DataFlowCall call | pathOutOfCallable1(mid, call, kind, cc) | + out = getAnOutNode(call, kind) + ) +} + +private predicate pathOutOfArgument(PathNodeMid mid, PostUpdateNode node, CallContext cc) { + exists( + PostUpdateNode n, ParameterNode p, DataFlowCallable callable, CallContext innercc, int i, + DataFlowCall call, ArgumentNode arg + | + mid.getNode() = n and + parameterValueFlowsToUpdate(p, n) and + innercc = mid.getCallContext() and + p.isParameterOf(callable, i) and + resolveReturn(innercc, callable, call) and + node.getPreUpdateNode() = arg and + arg.argumentOf(call, i) and + flow(node, unbind(mid.getConfiguration())) + | + if reducedViableImplInReturn(callable, call) + then cc = TReturn(callable, call) + else cc = TAnyCallContext() + ) +} + +/** + * Holds if data may flow from `mid` to the `i`th argument of `call` in `cc`. + */ +pragma[noinline] +private predicate pathIntoArg( + PathNodeMid mid, int i, CallContext cc, DataFlowCall call, boolean emptyAp +) { + exists(ArgumentNode arg, AccessPath ap | + arg = mid.getNode() and + cc = mid.getCallContext() and + arg.argumentOf(call, i) and + ap = mid.getAp() + | + ap instanceof AccessPathNil and emptyAp = true + or + ap instanceof AccessPathCons and emptyAp = false + ) +} + +pragma[noinline] +private predicate parameterCand(DataFlowCallable callable, int i, Configuration config) { + exists(ParameterNode p | + flow(p, config) and + p.isParameterOf(callable, i) + ) +} + +pragma[nomagic] +private predicate pathIntoCallable0( + PathNodeMid mid, DataFlowCallable callable, int i, CallContext outercc, DataFlowCall call, + boolean emptyAp +) { + pathIntoArg(mid, i, outercc, call, emptyAp) and + callable = resolveCall(call, outercc) and + parameterCand(callable, any(int j | j <= i and j >= i), mid.getConfiguration()) +} + +/** + * Holds if data may flow from `mid` to `p` through `call`. The contexts + * before and after entering the callable are `outercc` and `innercc`, + * respectively. + */ +private predicate pathIntoCallable( + PathNodeMid mid, ParameterNode p, CallContext outercc, CallContextCall innercc, DataFlowCall call +) { + exists(int i, DataFlowCallable callable, boolean emptyAp | + pathIntoCallable0(mid, callable, i, outercc, call, emptyAp) and + p.isParameterOf(callable, i) + | + if reducedViableImplInCallContext(_, callable, call) + then innercc = TSpecificCall(call, i, emptyAp) + else innercc = TSomeCall(p, emptyAp) + ) +} + +/** Holds if data may flow from `p` to a return of kind `kind`. */ +pragma[nomagic] +private predicate paramFlowsThrough( + ParameterNode p, ReturnKind kind, CallContextCall cc, AccessPathNil apnil, Configuration config +) { + exists(PathNodeMid mid, ReturnNode ret | + mid.getNode() = ret and + kind = ret.getKind() and + cc = mid.getCallContext() and + config = mid.getConfiguration() and + apnil = mid.getAp() + | + cc = TSomeCall(p, true) + or + exists(int i | cc = TSpecificCall(_, i, true) | + p.isParameterOf(returnNodeGetEnclosingCallable(ret), i) + ) + ) +} + +pragma[noinline] +private predicate pathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKind kind, CallContext cc, AccessPathNil apnil +) { + exists(ParameterNode p, CallContext innercc | + pathIntoCallable(mid, p, cc, innercc, call) and + paramFlowsThrough(p, kind, innercc, apnil, unbind(mid.getConfiguration())) and + not parameterValueFlowsThrough(p, kind, innercc) and + mid.getAp() instanceof AccessPathNil + ) +} + +/** + * Holds if data may flow from `mid` through a callable to the node `out`. + * The context `cc` is restored to its value prior to entering the callable. + */ +pragma[noinline] +private predicate pathThroughCallable( + PathNodeMid mid, OutNode out, CallContext cc, AccessPathNil apnil +) { + exists(DataFlowCall call, ReturnKind kind | + pathThroughCallable0(call, mid, kind, cc, apnil) and + out = getAnOutNode(call, kind) + ) +} + +pragma[noinline] +private predicate valuePathThroughCallable0( + DataFlowCall call, PathNodeMid mid, ReturnKind kind, CallContext cc +) { + exists(ParameterNode p, CallContext innercc | + pathIntoCallable(mid, p, cc, innercc, call) and + parameterValueFlowsThrough(p, kind, innercc) + ) +} + +private predicate valuePathThroughCallable(PathNodeMid mid, OutNode out, CallContext cc) { + exists(DataFlowCall call, ReturnKind kind | + valuePathThroughCallable0(call, mid, kind, cc) and + out = getAnOutNode(call, kind) + ) +} + +/** + * Holds if data can flow (inter-procedurally) from `source` to `sink`. + * + * Will only have results if `configuration` has non-empty sources and + * sinks. + */ +private predicate flowsTo( + PathNodeSource flowsource, PathNodeSink flowsink, Node source, Node sink, + Configuration configuration +) { + flowsource.getConfiguration() = configuration and + flowsource.getNode() = source and + pathSuccPlus(flowsource, flowsink) and + flowsink.getNode() = sink +} + +/** + * Holds if data can flow (inter-procedurally) from `source` to `sink`. + * + * Will only have results if `configuration` has non-empty sources and + * sinks. + */ +predicate flowsTo(Node source, Node sink, Configuration configuration) { + flowsTo(_, _, source, sink, configuration) +} diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowImplCommon.qll b/ql/src/semmle/go/dataflow/internal/DataFlowImplCommon.qll new file mode 100644 index 00000000..c89e861b --- /dev/null +++ b/ql/src/semmle/go/dataflow/internal/DataFlowImplCommon.qll @@ -0,0 +1,469 @@ +private import DataFlowImplSpecific::Private +import DataFlowImplSpecific::Public + +private ReturnNode getAReturnNodeOfKind(ReturnKind kind) { result.getKind() = kind } + +cached +private module ImplCommon { + /** + * Holds if `p` is the `i`th parameter of a viable dispatch target of `call`. + * The instance parameter is considered to have index `-1`. + */ + pragma[nomagic] + private predicate viableParam(DataFlowCall call, int i, ParameterNode p) { + p.isParameterOf(viableCallable(call), i) + } + + /** + * Holds if `arg` is a possible argument to `p` in `call`, taking virtual + * dispatch into account. + */ + cached + predicate viableParamArg(DataFlowCall call, ParameterNode p, ArgumentNode arg) { + exists(int i | + viableParam(call, i, p) and + arg.argumentOf(call, i) + ) + } + + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps, not taking call contexts into account. + */ + private predicate parameterValueFlowNoCtx(ParameterNode p, Node node) { + p = node + or + exists(Node mid | + parameterValueFlowNoCtx(p, mid) and + localFlowStep(mid, node) and + compatibleTypes(p.getType(), node.getType()) + ) + or + // flow through a callable + exists(Node arg | + parameterValueFlowNoCtx(p, arg) and + argumentValueFlowsThroughNoCtx(arg, node) and + compatibleTypes(p.getType(), node.getType()) + ) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps, not taking call contexts + * into account. + */ + private predicate parameterValueFlowsThroughNoCtx(ParameterNode p, ReturnKind kind) { + parameterValueFlowNoCtx(p, getAReturnNodeOfKind(kind)) + } + + pragma[nomagic] + private predicate argumentValueFlowsThroughNoCtx0( + DataFlowCall call, ArgumentNode arg, ReturnKind kind + ) { + exists(ParameterNode param | viableParamArg(call, param, arg) | + parameterValueFlowsThroughNoCtx(param, kind) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only value-preserving steps, + * not taking call contexts into account. + */ + private predicate argumentValueFlowsThroughNoCtx(ArgumentNode arg, OutNode out) { + exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThroughNoCtx0(call, arg, kind) | + out = getAnOutNode(call, kind) and + compatibleTypes(arg.getType(), out.getType()) + ) + } + + /** + * Holds if `arg` is the `i`th argument of `call` inside the callable + * `enclosing`, and `arg` may flow through `call`. + */ + pragma[noinline] + private predicate argumentOf( + DataFlowCall call, int i, ArgumentNode arg, DataFlowCallable enclosing + ) { + arg.argumentOf(call, i) and + argumentValueFlowsThroughNoCtx(arg, _) and + enclosing = arg.getEnclosingCallable() + } + + pragma[noinline] + private ParameterNode getAParameter(DataFlowCallable c) { result.getEnclosingCallable() = c } + + pragma[noinline] + private predicate viableParamArg0(int i, ArgumentNode arg, CallContext outercc, DataFlowCall call) { + exists(DataFlowCallable c | argumentOf(call, i, arg, c) | + outercc = TAnyCallContext() + or + outercc = TSomeCall(getAParameter(c), _) + or + exists(DataFlowCall other | outercc = TSpecificCall(other, _, _) | + reducedViableImplInCallContext(_, c, other) + ) + ) + } + + pragma[noinline] + private predicate viableParamArg1( + ParameterNode p, DataFlowCallable callable, int i, ArgumentNode arg, CallContext outercc, + DataFlowCall call + ) { + viableParamArg0(i, arg, outercc, call) and + callable = resolveCall(call, outercc) and + p.isParameterOf(callable, any(int j | j <= i and j >= i)) + } + + /** + * Holds if `arg` is a possible argument to `p`, in the call `call`, and + * `arg` may flow through `call`. The possible contexts before and after + * entering the callable are `outercc` and `innercc`, respectively. + */ + private predicate viableParamArg( + DataFlowCall call, ParameterNode p, ArgumentNode arg, CallContext outercc, + CallContextCall innercc + ) { + exists(int i, DataFlowCallable callable | viableParamArg1(p, callable, i, arg, outercc, call) | + if reducedViableImplInCallContext(_, callable, call) + then innercc = TSpecificCall(call, i, true) + else innercc = TSomeCall(p, true) + ) + } + + private CallContextCall getAValidCallContextForParameter(ParameterNode p) { + result = TSomeCall(p, _) + or + exists(DataFlowCall call, int i, DataFlowCallable callable | + result = TSpecificCall(call, i, _) and + p.isParameterOf(callable, i) and + reducedViableImplInCallContext(_, callable, call) + ) + } + + /** + * Holds if `p` can flow to `node` in the same callable using only + * value-preserving steps, in call context `cc`. + */ + private predicate parameterValueFlow(ParameterNode p, Node node, CallContextCall cc) { + p = node and + parameterValueFlowsThroughNoCtx(p, _) and + cc = getAValidCallContextForParameter(p) + or + exists(Node mid | + parameterValueFlow(p, mid, cc) and + localFlowStep(mid, node) and + compatibleTypes(p.getType(), node.getType()) + ) + or + // flow through a callable + exists(Node arg | + parameterValueFlow(p, arg, cc) and + argumentValueFlowsThrough(arg, node, cc) and + compatibleTypes(p.getType(), node.getType()) + ) + } + + /** + * Holds if `p` can flow to a return node of kind `kind` in the same + * callable using only value-preserving steps, in call context `cc`. + */ + cached + predicate parameterValueFlowsThrough(ParameterNode p, ReturnKind kind, CallContextCall cc) { + parameterValueFlow(p, getAReturnNodeOfKind(kind), cc) + } + + pragma[nomagic] + private predicate argumentValueFlowsThrough0( + DataFlowCall call, ArgumentNode arg, ReturnKind kind, CallContext cc + ) { + exists(ParameterNode param, CallContext innercc | + viableParamArg(call, param, arg, cc, innercc) and + parameterValueFlowsThrough(param, kind, innercc) + ) + } + + /** + * Holds if `arg` flows to `out` through a call using only value-preserving steps, + * in call context cc. + */ + cached + predicate argumentValueFlowsThrough(ArgumentNode arg, OutNode out, CallContext cc) { + exists(DataFlowCall call, ReturnKind kind | argumentValueFlowsThrough0(call, arg, kind, cc) | + out = getAnOutNode(call, kind) and + compatibleTypes(arg.getType(), out.getType()) + ) + } + + /** + * Holds if `p` can flow to the pre-update node of `n` in the same callable + * using only value-preserving steps. + */ + cached + predicate parameterValueFlowsToUpdate(ParameterNode p, PostUpdateNode n) { + parameterValueFlowNoCtx(p, n.getPreUpdateNode()) + } + + /** + * Holds if data can flow from `node1` to `node2` in one local step or a step + * through a value-preserving method. + */ + private predicate localValueStep(Node node1, Node node2) { + localFlowStep(node1, node2) or + argumentValueFlowsThrough(node1, node2, _) + } + + /* + * Calculation of `predicate store(Node node1, Content f, Node node2)`: + * There are three cases: + * - The base case: A direct local assignment given by `storeStep`. + * - A call to a method or constructor with two arguments, `arg1` and `arg2`, + * such the call has the side-effect `arg2.f = arg1`. + * - A call to a method that returns an object in which an argument has been + * stored. + * `storeViaSideEffect` covers the first two cases, and `storeReturn` covers + * the third case. + */ + + /** + * Holds if data can flow from `node1` to `node2` via a direct assignment to + * `f` or via a call that acts as a setter. + */ + cached + predicate store(Node node1, Content f, Node node2) { + storeViaSideEffect(node1, f, node2) or + storeReturn(node1, f, node2) + } + + private predicate storeViaSideEffect(Node node1, Content f, PostUpdateNode node2) { + storeStep(node1, f, node2) and readStep(_, f, _) + or + exists(DataFlowCall call, int i1, int i2 | + setterCall(call, i1, i2, f) and + node1.(ArgumentNode).argumentOf(call, i1) and + node2.getPreUpdateNode().(ArgumentNode).argumentOf(call, i2) and + compatibleTypes(node1.getTypeBound(), f.getType()) and + compatibleTypes(node2.getTypeBound(), f.getContainerType()) + ) + } + + pragma[nomagic] + private predicate setterInParam(ParameterNode p1, Content f, ParameterNode p2) { + exists(Node n1, PostUpdateNode n2 | + parameterValueFlowNoCtx(p1, n1) and + storeViaSideEffect(n1, f, n2) and + parameterValueFlowNoCtx(p2, n2.getPreUpdateNode()) and + p1 != p2 + ) + } + + pragma[nomagic] + private predicate setterCall(DataFlowCall call, int i1, int i2, Content f) { + exists(DataFlowCallable callable, ParameterNode p1, ParameterNode p2 | + setterInParam(p1, f, p2) and + callable = viableCallable(call) and + p1.isParameterOf(callable, i1) and + p2.isParameterOf(callable, i2) + ) + } + + pragma[noinline] + private predicate storeReturn0(DataFlowCall call, ReturnKind kind, ArgumentNode arg, Content f) { + exists(ParameterNode p | + viableParamArg(call, p, arg) and + setterReturn(p, f, kind) + ) + } + + private predicate storeReturn(Node node1, Content f, Node node2) { + exists(DataFlowCall call, ReturnKind kind | + storeReturn0(call, kind, node1, f) and + node2 = getAnOutNode(call, kind) and + compatibleTypes(node1.getTypeBound(), f.getType()) and + compatibleTypes(node2.getTypeBound(), f.getContainerType()) + ) + } + + private predicate setterReturn(ParameterNode p, Content f, ReturnKind kind) { + exists(Node n1, Node n2 | + parameterValueFlowNoCtx(p, n1) and + store(n1, f, n2) and + localValueStep*(n2, getAReturnNodeOfKind(kind)) + ) + } + + pragma[noinline] + private predicate read0(DataFlowCall call, ReturnKind kind, ArgumentNode arg, Content f) { + exists(ParameterNode p | + viableParamArg(call, p, arg) and + getter(p, f, kind) + ) + } + + /** + * Holds if data can flow from `node1` to `node2` via a direct read of `f` or + * via a getter. + */ + cached + predicate read(Node node1, Content f, Node node2) { + readStep(node1, f, node2) and storeStep(_, f, _) + or + exists(DataFlowCall call, ReturnKind kind | + read0(call, kind, node1, f) and + node2 = getAnOutNode(call, kind) and + compatibleTypes(node1.getTypeBound(), f.getContainerType()) and + compatibleTypes(node2.getTypeBound(), f.getType()) + ) + } + + private predicate getter(ParameterNode p, Content f, ReturnKind kind) { + exists(Node n1, Node n2 | + parameterValueFlowNoCtx(p, n1) and + read(n1, f, n2) and + localValueStep*(n2, getAReturnNodeOfKind(kind)) + ) + } + + cached + predicate localStoreReadStep(Node node1, Node node2) { + exists(Node mid1, Node mid2, Content f | + store(node1, f, mid1) and + localValueStep*(mid1, mid2) and + read(mid2, f, node2) + ) + } + + /** + * Holds if `call` passes an implicit or explicit instance argument, i.e., an + * expression that reaches a `this` parameter. + */ + private predicate callHasInstanceArgument(DataFlowCall call) { + exists(ArgumentNode arg | arg.argumentOf(call, -1)) + } + + cached + newtype TCallContext = + TAnyCallContext() or + TSpecificCall(DataFlowCall call, int i, boolean emptyAp) { + reducedViableImplInCallContext(_, _, call) and + (emptyAp = true or emptyAp = false) and + ( + exists(call.getArgument(i)) + or + i = -1 and callHasInstanceArgument(call) + ) + } or + TSomeCall(ParameterNode p, boolean emptyAp) { emptyAp = true or emptyAp = false } or + TReturn(DataFlowCallable c, DataFlowCall call) { reducedViableImplInReturn(c, call) } + + cached + newtype TReturnPosition = + TReturnPosition0(DataFlowCallable c, ReturnKind kind) { returnPosition(_, c, kind) } +} +import ImplCommon + +pragma[noinline] +private predicate returnPosition(ReturnNode ret, DataFlowCallable c, ReturnKind kind) { + c = returnNodeGetEnclosingCallable(ret) and + kind = ret.getKind() +} + +/** + * A call context to restrict the targets of virtual dispatch and match the + * call sites of flow into a method with flow out of a method. + * + * There are four cases: + * - `TAnyCallContext()` : No restrictions on method flow. + * - `TSpecificCall(DataFlowCall call, int i)` : Flow entered through the `i`th + * parameter at the given `call`. This call improves the set of viable + * dispatch targets for at least one method call in the current callable. + * - `TSomeCall(ParameterNode p)` : Flow entered through parameter `p`. The + * originating call does not improve the set of dispatch targets for any + * method call in the current callable and was therefore not recorded. + * - `TReturn(Callable c, DataFlowCall call)` : Flow reached `call` from `c` and + * this dispatch target of `call` implies a reduced set of dispatch origins + * to which data may flow if it should reach a `return` statement. + */ +abstract class CallContext extends TCallContext { + abstract string toString(); +} + +class CallContextAny extends CallContext, TAnyCallContext { + override string toString() { result = "CcAny" } +} + +abstract class CallContextCall extends CallContext { } + +class CallContextSpecificCall extends CallContextCall, TSpecificCall { + override string toString() { + exists(DataFlowCall call, int i | this = TSpecificCall(call, i, _) | + result = "CcCall(" + call + ", " + i + ")" + ) + } +} + +class CallContextSomeCall extends CallContextCall, TSomeCall { + override string toString() { result = "CcSomeCall" } +} + +class CallContextReturn extends CallContext, TReturn { + override string toString() { + exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")") + } +} + +/** A callable tagged with a relevant return kind. */ +class ReturnPosition extends TReturnPosition0 { + private DataFlowCallable c; + + private ReturnKind kind; + + ReturnPosition() { this = TReturnPosition0(c, kind) } + + /** Gets the callable. */ + DataFlowCallable getCallable() { result = c } + + /** Gets the return kind. */ + ReturnKind getKind() { result = kind } + + /** Gets a textual representation of this return position. */ + string toString() { result = "[" + kind + "] " + c } +} + +pragma[noinline] +DataFlowCallable returnNodeGetEnclosingCallable(ReturnNode ret) { + result = ret.getEnclosingCallable() +} + +pragma[noinline] +ReturnPosition getReturnPosition(ReturnNode ret) { + exists(DataFlowCallable c, ReturnKind k | returnPosition(ret, c, k) | + result = TReturnPosition0(c, k) + ) +} + +bindingset[cc, callable] +predicate resolveReturn(CallContext cc, DataFlowCallable callable, DataFlowCall call) { + cc instanceof CallContextAny and callable = viableCallable(call) + or + exists(DataFlowCallable c0, DataFlowCall call0 | + call0.getEnclosingCallable() = callable and + cc = TReturn(c0, call0) and + c0 = prunedViableImplInCallContextReverse(call0, call) + ) +} + +bindingset[call, cc] +DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) { + exists(DataFlowCall ctx | cc = TSpecificCall(ctx, _, _) | + if reducedViableImplInCallContext(call, _, ctx) + then result = prunedViableImplInCallContext(call, ctx) + else result = viableCallable(call) + ) + or + result = viableCallable(call) and cc instanceof CallContextSomeCall + or + result = viableCallable(call) and cc instanceof CallContextAny + or + result = viableCallable(call) and cc instanceof CallContextReturn +} diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowImplSpecific.qll b/ql/src/semmle/go/dataflow/internal/DataFlowImplSpecific.qll new file mode 100644 index 00000000..ee044c5e --- /dev/null +++ b/ql/src/semmle/go/dataflow/internal/DataFlowImplSpecific.qll @@ -0,0 +1,11 @@ +/** + * Provides Go-specific definitions for use in the data flow library. + */ +module Private { + import DataFlowPrivate + import DataFlowDispatch +} + +module Public { + import DataFlowUtil +} diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowPrivate.qll b/ql/src/semmle/go/dataflow/internal/DataFlowPrivate.qll new file mode 100644 index 00000000..9154fd65 --- /dev/null +++ b/ql/src/semmle/go/dataflow/internal/DataFlowPrivate.qll @@ -0,0 +1,243 @@ +private import go +private import DataFlowUtil + +private newtype TReturnKind = + TSingleReturn() + or + TMultiReturn(int i) { exists(SignatureType st | exists(st.getResultType(i))) } + +/** + * A return kind. A return kind describes how a value can be returned + * from a callable. For Go, this is either a return of a single value + * or of one of multiple values. + */ +class ReturnKind extends TReturnKind { + /** Gets a textual representation of this return kind. */ + string toString() { + this = TSingleReturn() and + result = "return" + or + exists(int i | this = TMultiReturn(i) | + result = "return[" + i + "]" + ) + } +} + +/** A data flow node that represents returning a value from a function. */ +class ReturnNode extends ResultNode { + ReturnKind kind; + + ReturnNode() { + exists(int nr | nr = fd.getType().getNumResult() | + if nr = 1 then + kind = TSingleReturn() + else + kind = TMultiReturn(i) + ) + } + + /** Gets the kind of this returned value. */ + ReturnKind getKind() { result = kind } +} + +/** A data flow node that represents the output of a call. */ +class OutNode extends DataFlow::Node { + DataFlow::CallNode call; + + int i; + + OutNode() { + this = call.getResult() and + i = -1 + or + this = call.getResult(i) + } + + /** Gets the underlying call. */ + DataFlowCall getCall() { result = call.asExpr() } +} + +/** + * Gets a node that can read the value returned from `call` with return kind + * `kind`. + */ +OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { + exists(DataFlow::CallNode c | c.asExpr() = call | + kind = TSingleReturn() and + result = c.getResult() + or + exists(int i | kind = TMultiReturn(i) | + result = c.getResult(i) + ) + ) +} + +/** + * Holds if data can flow from `node1` to `node2` in a way that loses the + * calling context. For example, this would happen with flow through a + * global or static variable. + */ +predicate jumpStep(Node n1, Node n2) { + exists(ValueEntity v, Write w | + not v instanceof SsaSourceVariable and + w.writes(v, n1) and + n2 = v.getARead() + ) +} + +/** + * Holds if `call` passes an implicit or explicit qualifier, i.e., a + * `this` parameter. + */ +predicate callHasQualifier(CallExpr call) { exists(call.getQualifier()) } + +private newtype TContent = + TFieldContent(Field f) or + TCollectionContent() or + TArrayContent() or + TPointerContent(PointerType p) + +/** + * A reference contained in an object. Examples include instance fields, the + * contents of a collection object, the contents of an array or pointer. + */ +class Content extends TContent { + /** Gets a textual representation of this element. */ + abstract string toString(); + + predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0 + } + + /** Gets the type of the object containing this content. */ + abstract Type getContainerType(); + + /** Gets the type of this content. */ + abstract Type getType(); +} + +private class FieldContent extends Content, TFieldContent { + Field f; + + FieldContent() { this = TFieldContent(f) } + + override string toString() { result = f.toString() } + + override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { + f.getDeclaration().hasLocationInfo(path, sl, sc, el, ec) + } + + override Type getContainerType() { result = f.getDeclaringType() } + + override Type getType() { result = f.getType() } +} + +private class CollectionContent extends Content, TCollectionContent { + override string toString() { result = "collection" } + + override Type getContainerType() { none() } + + override Type getType() { none() } +} + +private class ArrayContent extends Content, TArrayContent { + override string toString() { result = "array" } + + override Type getContainerType() { none() } + + override Type getType() { none() } +} + +private class PointerContent extends Content, TPointerContent { + override string toString() { result = "pointer" } + + override Type getContainerType() { this = TPointerContent(result) } + + override Type getType() { result = getContainerType().(PointerType).getBaseType() } +} + +/** + * Holds if data can flow from `node1` to `node2` via an assignment to `c`. + * Thus, `node2` references an object with a field `f` that contains the + * value of `node1`. + */ +predicate storeStep(Node node1, Content c, PostUpdateNode node2) { + exists(Write w, Field f | + w.writesField(node2.getPreUpdateNode(), f, node1) and + c = TFieldContent(f) + ) + or + node1 = node2.(AddressOperationNode).getOperand() and + c = TPointerContent(node2.getType()) +} + +/** + * Holds if data can flow from `node1` to `node2` via a read of `f`. + * Thus, `node1` references an object with a field `f` whose value ends up in + * `node2`. + */ +predicate readStep(Node node1, Content f, Node node2) { + node1 = node2.(PointerDereferenceNode).getOperand() and + f = TPointerContent(node1.getType()) + or + exists(FieldReadNode read | + node2 = read and + node1 = read.getBase() and + f = TFieldContent(read.getField()) + ) +} + +/** + * Gets a representative (boxed) type for `t` for the purpose of pruning + * possible flow. A single type is used for all numeric types to account for + * numeric conversions, and otherwise the erasure is used. + */ +Type getErasedRepr(Type t) { + result = t // stub implementation +} + +/** Gets a string representation of a type returned by `getErasedRepr`. */ +string ppReprType(Type t) { result = t.toString() } + +/** + * Holds if `t1` and `t2` are compatible, that is, whether data can flow from + * a node of type `t1` to a node of type `t2`. + */ +pragma[inline] +predicate compatibleTypes(Type t1, Type t2) { + any() // stub implementation +} + +////////////////////////////////////////////////////////////////////////////// +// Java QL library compatibility wrappers +////////////////////////////////////////////////////////////////////////////// +/** A node that performs a type cast. */ +class CastNode extends ExprNode { + override ConversionExpr expr; +} + +class DataFlowCallable = FuncDef; + +class DataFlowExpr = Expr; + +class DataFlowType = Type; + +class DataFlowLocation = Location; + +/** A function call relevant for data flow. */ +class DataFlowCall extends Expr { + DataFlow::CallNode call; + + DataFlowCall() { this = call.asExpr() } + + /** + * Gets the nth argument for this call. + */ + Node getArgument(int n) { result = call.getArgument(n) } + + /** Gets the data flow node corresponding to this call. */ + ExprNode getNode() { result = call } + + /** Gets the enclosing callable of this call. */ + DataFlowCallable getEnclosingCallable() { result = this.getEnclosingFunction() } +} diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll new file mode 100644 index 00000000..9851766e --- /dev/null +++ b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll @@ -0,0 +1,889 @@ +/** + * Provides Go-specific definitions for use in the data flow library. + */ + +import go +import semmle.go.dataflow.FunctionInputsAndOutputs + +cached +private newtype TNode = + MkInstructionNode(IR::Instruction insn) or + MkSsaNode(SsaDefinition ssa) or + MkGlobalFunctionNode(Function f) + +/** + * A node in a data flow graph. + * + * A node can be either an IR instruction or an SSA definition. + * Such nodes are created with `DataFlow::instructionNode` + * and `DataFlow::ssaNode` respectively. + */ +class Node extends TNode { + /** Gets the function to which this node belongs. */ + ControlFlow::Root getRoot() { none() } // overridden in subclasses + + /** INTERNAL: Use `getRoot()` instead. */ + FuncDef getEnclosingCallable() { result = getRoot() } + + /** Gets the type of this node. */ + Type getType() { none() } // overridden in subclasses + + /** Gets the expression corresponding to this node, if any. */ + Expr asExpr() { none() } // overridden in subclasses + + /** Gets the parameter corresponding to this node, if any. */ + Parameter asParameter() { none() } // overridden in subclasses + + /** Gets the IR instruction corresponding to this node, if any. */ + IR::Instruction asInstruction() { none() } // overridden in subclasses + + /** Gets a textual representation of the kind of this data-flow node. */ + string getNodeKind() { none() } // overridden in subclasses + + /** Gets a textual representation of this element. */ + string toString() { result = "data-flow node" } // overridden in subclasses + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + filepath = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } + + /** + * Gets an upper bound on the type of this node. + */ + Type getTypeBound() { result = getType() } + + /** Gets the floating-point value this data-flow node contains, if any. */ + float getFloatValue() { result = this.asExpr().getFloatValue() } + + /** + * Gets the integer value this data-flow node contains, if any. + * + * Note that this does not have a result if the value is too large to fit in a + * 32-bit signed integer type. + */ + int getIntValue() { result = this.asInstruction().getIntValue() } + + /** Gets either `getFloatValue` or `getIntValue`. */ + float getNumericValue() { result = this.asInstruction().getNumericValue() } + + /** + * Holds if the complex value this data-flow node contains has real part `real` and imaginary + * part `imag`. + */ + predicate hasComplexValue(float real, float imag) { + this.asInstruction().hasComplexValue(real, imag) + } + + /** Gets the string value this data-flow node contains, if any. */ + string getStringValue() { result = this.asInstruction().getStringValue() } + + /** + * Gets the string representation of the exact value this data-flow node + * contains, if any. + * + * For example, for the constant 3.141592653589793238462, this will + * result in 1570796326794896619231/500000000000000000000 + */ + string getExactValue() { result = this.asInstruction().getExactValue() } + + /** Gets the Boolean value this data-flow node contains, if any. */ + boolean getBoolValue() { result = this.asInstruction().getBoolValue() } + + /** Holds if the value of this data-flow node is known at compile time. */ + predicate isConst() { this.asInstruction().isConst() } + + /** + * Holds if the result of this instruction is known at compile time, and is guaranteed not to + * depend on the platform where it is evaluated. + */ + predicate isPlatformIndependentConstant() { + this.asInstruction().isPlatformIndependentConstant() + } + + /** + * Gets a data-flow node to which data may flow from this node in one (intra-procedural) step. + */ + Node getASuccessor() { localFlowStep(this, result) } + + /** + * Gets a data-flow node from which data may flow to this node in one (intra-procedural) step. + */ + Node getAPredecessor() { this = result.getASuccessor() } +} + +/** + * An IR instruction, viewed as a node in a data flow graph. + */ +class InstructionNode extends Node, MkInstructionNode { + IR::Instruction insn; + + InstructionNode() { this = MkInstructionNode(insn) } + + override IR::Instruction asInstruction() { result = insn } + + override ControlFlow::Root getRoot() { result = insn.getRoot() } + + override Type getType() { result = insn.getResultType() } + + override string getNodeKind() { result = insn.getInsnKind() } + + override string toString() { result = insn.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + insn.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +class ExprNode extends InstructionNode { + override IR::EvalInstruction insn; + Expr expr; + + ExprNode() { expr = insn.getExpr() } + + override Expr asExpr() { result = expr } + + Expr getExpr() { result = expr } +} + +/** + * An SSA variable, viewed as a node in a data flow graph. + */ +class SsaNode extends Node, MkSsaNode { + SsaDefinition ssa; + + SsaNode() { this = MkSsaNode(ssa) } + + /** Gets the node whose value is stored in this SSA variable, if any. */ + Node getInit() { result = instructionNode(ssa.(SsaExplicitDefinition).getRhs()) } + + /** Gets a use of this SSA variable. */ + InstructionNode getAUse() { result = instructionNode(ssa.getVariable().getAUse()) } + + /** Gets the program variable corresponding to this SSA variable. */ + SsaSourceVariable getSourceVariable() { result = ssa.getSourceVariable() } + + /** Gets the unique definition of this SSA variable. */ + SsaDefinition getDefinition() { result = ssa } + + override ControlFlow::Root getRoot() { result = ssa.getRoot() } + + override Type getType() { result = ssa.getSourceVariable().getType() } + + override string getNodeKind() { result = "SSA variable " + ssa.getSourceVariable().getName() } + + override string toString() { result = ssa.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + ssa.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** A function, viewed as a node in a data flow graph. */ +abstract class FunctionNode extends Node { + /** Gets the `i`th parameter of this function. */ + abstract ParameterNode getParameter(int i); + + /** Gets a parameter of this function. */ + ParameterNode getAParameter() { result = this.getParameter(_) } + + /** Gets the number of parameters declared on this function. */ + int getNumParameter() { result = count(this.getAParameter()) } + + /** Gets the name of this function, if it has one. */ + abstract string getName(); + + /** + * Gets the dataflow node holding the value of the receiver, if any. + */ + abstract ReceiverNode getReceiver(); +} + +/** A representation of a function that is declared in the module scope. */ +class GlobalFunctionNode extends FunctionNode, MkGlobalFunctionNode { + Function func; + + GlobalFunctionNode() { this = MkGlobalFunctionNode(func) } + + override ParameterNode getParameter(int i) { + result = parameterNode(func.(DeclaredFunction).getDecl().getParameter(i)) + } + + override string getName() { result = func.getName() } + + /** Gets the function this node corresponds to. */ + Function getFunction() { result = func } + + override ReceiverNode getReceiver() { result = receiverNode(func.(Method).getReceiver()) } + + override string getNodeKind() { result = "function " + func.getName() } + + override string toString() { result = "function " + func.getName() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + func + .(DeclaredFunction) + .getDecl() + .hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + or + not func instanceof DeclaredFunction and + FunctionNode.super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } +} + +/** A representation of the function that is defined by a function literal. */ +class FuncLitNode extends FunctionNode, ExprNode { + override FuncLit expr; + + override ParameterNode getParameter(int i) { result = parameterNode(expr.getParameter(i)) } + + override string getName() { none() } + + override ReceiverNode getReceiver() { none() } + + override string toString() { result = "function literal" } +} + +/** A data flow node that represents a call. */ +class CallNode extends ExprNode { + override CallExpr expr; + + /** Gets the declared target of this call */ + Function getTarget() { result = expr.getTarget() } + + /** Get the declaration of a possible target of this call. See `CallExpr.getACallee`. */ + FuncDecl getACallee() { result = expr.getACallee() } + + /** Gets the name of the function or method being called, if it can be determined. */ + string getCalleeName() { result = expr.getTarget().getName() } + + /** Gets the data flow node specifying the function to be called. */ + Node getCalleeNode() { result = exprNode(expr.getCalleeExpr()) } + + /** Gets the underlying call. */ + CallExpr getCall() { result = this.getExpr() } + + /** + * Gets the data flow node corresponding to the `i`th argument of this call. + * + * Note that the first argument in calls to the built-in function `make` is a type, which is + * not a data-flow node. It is skipped for the purposes of this predicate, so the (syntactically) + * second argument becomes the first argument in terms of data flow. + * + * For calls of the form `f(g())` where `g` has multiple results, the arguments of the call to + * `i` are the (implicit) element extraction nodes for the call to `g`. + */ + Node getArgument(int i) { + if expr.getArgument(0).getType() instanceof TupleType + then result = extractTupleElement(exprNode(expr.getArgument(0)), i) + else + result = rank[i + 1](Expr arg, int j | arg = expr.getArgument(j) | exprNode(arg) order by j) + } + + /** Gets the data flow node corresponding to an argument of this call. */ + Node getAnArgument() { result = this.getArgument(_) } + + /** Gets the number of arguments of this call, if it can be determined. */ + int getNumArgument() { result = count(this.getAnArgument()) } + + /** Gets a function passed as the `i`th argument of this call. */ + FunctionNode getCallback(int i) { result.getASuccessor*() = this.getArgument(i) } + + /** Gets the data-flow node corresponding to the `i`th result of this call. */ + Node getResult(int i) { result = extractTupleElement(this, i) } + + /** + * Gets the data-flow node corresponding to the result of this call. + * + * Note that this predicate is not defined for calls with multiple results; use the one-argument + * variant `getResult(i)` for such calls. + */ + Node getResult() { not getType() instanceof TupleType and result = this } +} + +/** A data flow node that represents a call to a method. */ +class MethodCallNode extends CallNode { + MethodCallNode() { expr.getTarget() instanceof Method } + + override Method getTarget() { result = expr.getTarget() } + + override MethodDecl getACallee() { result = expr.getACallee() } + + /** Gets the data flow node corresponding to the receiver of this call. */ + Node getReceiver() { + exists(SelectorExpr s | exprNode(s).getASuccessor*() = this.getCalleeNode() | + result = exprNode(s.getBase()) + ) + } +} + +/** A representation of a receiver initialization. */ +class ReceiverNode extends SsaNode { + override SsaExplicitDefinition ssa; + ReceiverVariable recv; + + ReceiverNode() { ssa.getInstruction() = IR::initRecvInstruction(recv) } + + ReceiverVariable asReceiverVariable() { result = recv } + + predicate isReceiverOf(FuncDef fd) { recv = fd.(MethodDecl).getReceiver() } +} + +/** A representation of a parameter initialization. */ +class ParameterNode extends SsaNode { + override SsaExplicitDefinition ssa; + Parameter parm; + + ParameterNode() { ssa.getInstruction() = IR::initParamInstruction(parm) } + + override Parameter asParameter() { result = parm } + + predicate isParameterOf(FuncDef fd, int i) { parm = fd.getParameter(i) } +} + +/** + * A node associated with an object after an operation that might have + * changed its state. + * + * This can be either the argument to a callable after the callable returns + * (which might have mutated the argument), or the qualifier of a field after + * an update to the field. + * + * Nodes corresponding to AST elements, for example `ExprNode`, usually refer + * to the value before the update with the exception of `ClassInstanceExpr`, + * which represents the value after the constructor has run. + */ +class PostUpdateNode extends Node { + Node preupd; + + PostUpdateNode() { + ( + preupd instanceof AddressOperationNode or + any(Write w).writesField(preupd, _, _) + ) and + ( + preupd = this.(SsaNode).getAUse() + or + preupd = this and + not exists(getAPredecessor()) + ) + } + + /** + * Gets the node before the state update. + */ + Node getPreUpdateNode() { result = preupd } +} + +/** + * A data-flow node that occurs as an argument in a call, including receiver arguments. + */ +class ArgumentNode extends Node { + CallNode c; + int i; + + ArgumentNode() { + this = c.getArgument(i) + or + this = c.(MethodCallNode).getReceiver() and + i = -1 + } + + /** + * Holds if this argument occurs at the given position in the given call. + * + * The receiver argument is considered to have index `-1`. + */ + predicate argumentOf(CallExpr call, int pos) { + call = c.asExpr() and + pos = i + } + + /** + * Gets the `CallNode` this is an argument to. + */ + CallNode getCall() { result = c } +} + +/** + * A node whose value is returned as a result from a function. + * + * This can either be a node corresponding to an expression in a return statement, + * or a node representing the current value of a named result variable at the exit + * of the function. + */ +class ResultNode extends InstructionNode { + FuncDef fd; + int i; + + ResultNode() { + exists(IR::ReturnInstruction ret | ret.getRoot() = fd | insn = ret.getResult(i)) + or + insn.(IR::ReadResultInstruction).reads(fd.getResultVar(i)) + } +} + +class ReadNode extends InstructionNode { + override IR::ReadInstruction insn; + + /** + * Holds if this data-flow node evaluates to value of `v`, which is a value entity, that is, a + * constant, variable, field, function, or method. + */ + predicate reads(ValueEntity v) { insn.reads(v) } + + /** + * Holds if this data-flow node reads the value of SSA variable `v`. + */ + predicate readsSsaVariable(SsaVariable v) { insn = v.getAUse() } + + /** + * Holds if this data-flow node reads the value of field `f` on the value of `base`. + */ + predicate readsField(Node base, Field f) { insn.readsField(base.asInstruction(), f) } + + /** + * Holds if this data-flow node looks up method `m` on the value of `receiver`. + */ + predicate readsMethod(Node receiver, Method m) { insn.readsMethod(receiver.asInstruction(), m) } + + /** + * Holds if this data-flow node reads the value of element `index` on the value of `base`. + */ + predicate readsElement(Node base, Node index) { + insn.readsElement(base.asInstruction(), index.asInstruction()) + } +} + +/** + * A data-flow node that reads an element of an array, map, slice or string. + */ +class ElementReadNode extends ReadNode { + override IR::ElementReadInstruction insn; + + /** Gets the data-flow node representing the base from which the element is read. */ + Node getBase() { result = instructionNode(insn.getBase()) } + + /** Gets the data-flow node representing the index of the element being read. */ + Node getIndex() { result = instructionNode(insn.getIndex()) } + + /** Holds if this data-flow node reads element `index` of `base`. */ + predicate reads(Node base, Node index) { getBase() = base and getIndex() = index } +} + +/** + * A data-flow node corresponding to an expression with a binary operator. + */ +class BinaryOperationNode extends Node { + Node left; + Node right; + string op; + + BinaryOperationNode() { + exists(BinaryExpr bin | bin = asExpr() | + left = exprNode(bin.getLeftOperand()) and + right = exprNode(bin.getRightOperand()) and + op = bin.getOperator() + ) + or + exists(IR::EvalCompoundAssignRhsInstruction rhs, CompoundAssignStmt assgn, string o | + rhs = asInstruction() and assgn = rhs.getAssignment() and o = assgn.getOperator() + | + left = exprNode(assgn.getLhs()) and + right = exprNode(assgn.getRhs()) and + op = o.substring(0, o.length()-1) + ) + or + exists(IR::EvalIncDecRhsInstruction rhs, IncDecStmt ids | + rhs = asInstruction() and ids = rhs.getStmt() + | + left = exprNode(ids.getExpr()) and + right = instructionNode(any(IR::EvalImplicitOneInstruction one | one.getStmt() = ids)) and + op = ids.getOperator().charAt(0) + ) + } + + /** Holds if this operation may have observable side effects. */ + predicate mayHaveSideEffects() { asExpr().mayHaveOwnSideEffects() } + + /** Gets the left operand of this operation. */ + Node getLeftOperand() { result = left } + + /** Gets the right operand of this operation. */ + Node getRightOperand() { result = right } + + /** Gets an operand of this operation. */ + Node getAnOperand() { result = left or result = right } + + /** Gets the operator of this operation. */ + string getOperator() { result = op } +} + +/** + * An IR instruction corresponding to an expression with a unary operator. + */ +class UnaryOperationNode extends ExprNode { + UnaryOperationNode() { + expr instanceof UnaryExpr or + expr instanceof StarExpr + } + + /** Holds if this operation may have observable side effects. */ + predicate mayHaveSideEffects() { expr.mayHaveOwnSideEffects() } + + /** Gets the operand of this operation. */ + Node getOperand() { + result = exprNode(expr.(UnaryExpr).getOperand()) or + result = exprNode(expr.(StarExpr).getBase()) + } + + /** Gets the operator of this operation. */ + string getOperator() { + result = expr.(UnaryExpr).getOperator() + or + expr instanceof StarExpr and result = "*" + } +} + +/** + * A pointer-dereference instruction. + */ +class PointerDereferenceNode extends UnaryOperationNode { + PointerDereferenceNode() { expr instanceof StarExpr or expr instanceof DerefExpr } +} + +/** + * An address-of instruction. + */ +class AddressOperationNode extends UnaryOperationNode { + override AddressExpr expr; +} + +class FieldReadNode extends ReadNode { + override IR::FieldReadInstruction insn; + + Node getBase() { result = instructionNode(insn.getBase()) } + + Field getField() { result = insn.getField() } + + string getFieldName() { result = this.getField().getName() } +} + +class MethodReadNode extends ReadNode { + override IR::MethodReadInstruction insn; + + Node getReceiver() { result = instructionNode(insn.getReceiver()) } + + Method getMethod() { result = insn.getMethod() } + + string getMethodName() { result = this.getMethod().getName() } +} + +/** + * An IR instruction performing a relational comparison using `<`, `<=`, `>` or `>=`. + */ +class RelationalComparisonNode extends BinaryOperationNode, ExprNode { + override RelationalComparisonExpr expr; + + /** Holds if this comparison evaluates to `outcome` iff `lesser <= greater + bias`. */ + predicate leq(boolean outcome, Node lesser, Node greater, int bias) { + outcome = true and + lesser = exprNode(expr.getLesserOperand()) and + greater = exprNode(expr.getGreaterOperand()) and + (if expr.isStrict() then bias = -1 else bias = 0) + or + outcome = false and + lesser = exprNode(expr.getGreaterOperand()) and + greater = exprNode(expr.getLesserOperand()) and + (if expr.isStrict() then bias = 0 else bias = -1) + } +} + +/** + * An IR instruction performing an equality test using `==` or `!=`. + */ +class EqualityTestNode extends BinaryOperationNode, ExprNode { + override EqualityTestExpr expr; + + /** Holds if this comparison evaluates to `outcome` iff `lhs == rhs`. */ + predicate eq(boolean outcome, Node lhs, Node rhs) { + outcome = expr.getPolarity() and + expr.hasOperands(lhs.asExpr(), rhs.asExpr()) + } +} + +/** + * A model of a function specifying that the function copies input values from + * a parameter or qualifier to a result. + * + * Note that this only models verbatim copying. Flow that does not preserve exact + * values should be modeled by `TaintTracking::FunctionModel` instead. + */ +abstract class FunctionModel extends Function { + abstract predicate hasDataFlow(FunctionInput input, FunctionOutput output); +} + +/** + * Gets the `Node` corresponding to `insn`. + */ +InstructionNode instructionNode(IR::Instruction insn) { result = MkInstructionNode(insn) } + +/** + * Gets the `Node` corresponding to `e`. + */ +ExprNode exprNode(Expr e) { result.asExpr() = e.stripParens() } + +/** + * Gets the `Node` corresponding to the value of `p` at function entry. + */ +ParameterNode parameterNode(Parameter p) { result.asParameter() = p } + +/** + * Gets the `Node` corresponding to the value of `r` at function entry. + */ +ReceiverNode receiverNode(ReceiverVariable r) { result.asReceiverVariable() = r } + +/** + * Gets the data-flow node corresponding to SSA variable `v`. + */ +SsaNode ssaNode(SsaVariable v) { result.getDefinition() = v.getDefinition() } + +/** + * Gets the data-flow node corresponding to the `i`th element of tuple `t` (which is either a call + * with multiple results or an iterator in a range loop). + */ +Node extractTupleElement(Node t, int i) { + exists(IR::Instruction insn | t = instructionNode(insn) | + result = instructionNode(IR::extractTupleElement(insn, i)) + ) +} + +/** + * Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local + * (intra-procedural) step. + */ +cached +predicate localFlowStep(Node nodeFrom, Node nodeTo) { + // Instruction -> Instruction + exists(Expr pred, Expr succ | + succ.(LogicalBinaryExpr).getAnOperand() = pred or + succ.(ConversionExpr).getOperand() = pred or + succ.(TypeAssertExpr).getExpression() = pred + | + nodeFrom = exprNode(pred) and + nodeTo = exprNode(succ) + ) + or + // Instruction -> SSA + exists(IR::Instruction pred, SsaExplicitDefinition succ | + succ.getRhs() = pred and + nodeFrom = MkInstructionNode(pred) and + nodeTo = MkSsaNode(succ) + ) + or + // SSA -> SSA + exists(SsaDefinition pred, SsaDefinition succ | + succ.(SsaVariableCapture).getSourceVariable() = pred.(SsaExplicitDefinition).getSourceVariable() or + succ.(SsaPseudoDefinition).getAnInput() = pred + | + nodeFrom = MkSsaNode(pred) and + nodeTo = MkSsaNode(succ) + ) + or + // SSA -> Instruction + exists(SsaDefinition pred, IR::Instruction succ | + succ = pred.getVariable().getAUse() and + nodeFrom = MkSsaNode(pred) and + nodeTo = MkInstructionNode(succ) + ) + or + // GlobalFunctionNode -> use + nodeTo = MkGlobalFunctionNode(nodeFrom.asExpr().(FunctionName).getTarget()) + or + // step through function model + exists(FunctionModel m, CallNode c, FunctionInput inp, FunctionOutput outp | + c = m.getACall() and + m.hasDataFlow(inp, outp) and + nodeFrom = inp.getNode(c) and + nodeTo = outp.getNode(c) + ) +} + +/** + * Holds if data flows from `source` to `sink` in zero or more local + * (intra-procedural) steps. + */ +predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) } + +/** + * An SSA variable, possibly with a chain of field reads on it. + */ +private newtype SsaWithFields = + Root(SsaVariable v) or + Step(SsaWithFields base, Field f) { exists(accessPathAux(base, f)) } + +/** + * Gets a representation of `nd` as an ssa-with-fields value if there is one. + */ +private SsaWithFields accessPath(Node nd) { + exists(SsaVariable v | nd.asInstruction() = v.getAUse() | result = Root(v)) + or + exists(SsaWithFields base, Field f | nd = accessPathAux(base, f) | result = Step(base, f)) +} + +/** + * Gets a data-flow node that reads a field `f` from a node that is represented + * by ssa-with-fields value `base`. + */ +private Node accessPathAux(SsaWithFields base, Field f) { + exists(IR::FieldReadInstruction fr | fr = result.asInstruction() | + base = accessPath(instructionNode(fr.getBase())) and + f = fr.getField() + ) +} + +/** + * A guard that validates some expression. + * + * To use this in a configuration, extend the class and provide a + * characteristic predicate precisely specifying the guard, and override + * `checks` to specify what is being validated and in which branch. + * + * It is important that all extending classes in scope are disjoint. + */ +abstract class BarrierGuard extends Node { + /** Holds if this guard validates `e` upon evaluating to `branch`. */ + abstract predicate checks(Expr e, boolean branch); + + /** Gets a node guarded by this guard. */ + final Node getAGuardedNode() { + exists(ControlFlow::ConditionGuardNode guard, Node nd | + guards(guard, nd, accessPath(result)) and + guard.dominates(result.asInstruction().getBasicBlock()) + ) + } + + /** + * Holds if `guard` markes a point in the control-flow graph where this node + * is known to validate `nd`, which is represented by `ap`. + * + * This predicate exists to enforce a good join order in `getAGuardedNode`. + */ + pragma[noinline] + private predicate guards(ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields ap) { + guards(guard, nd) and + ap = accessPath(nd) + } + + /** + * Holds if `guard` markes a point in the control-flow graph where this node + * is known to validate `nd`. + */ + private predicate guards(ControlFlow::ConditionGuardNode guard, Node nd) { + exists(boolean branch | + this.checks(nd.asExpr(), branch) and + guard.ensures(this, branch) + ) + or + exists( + Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p, CallNode c, + Node resNode, Node check, boolean outcome + | + guardingFunction(f, inp, outp, p) and + c = f.getACall() and + nd = inp.getNode(c) and + localFlow(outp.getNode(c), resNode) and + p.checkOn(check, outcome, resNode) and + guard.ensures(check, outcome) + ) + } + + /** + * Holds if whenever `p` holds of output `outp` of function `f`, this node + * is known to validate the input `inp` of `f`. + * + * We check this by looking for guards on `inp` that dominate a `return` statement that + * is the only `return` in `f` that can return `true`. This means that if `f` returns `true`, + * the guard must have been satisfied. (Similar reasoning is applied for statements returning + * `false` or a non-`nil` value.) + */ + private predicate guardingFunction( + Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p + ) { + exists(ControlFlow::ConditionGuardNode guard, Node arg, FuncDecl fd, Node ret | + fd.getFunction() = f and + guards(guard, arg) and + localFlow(inp.getExitNode(fd), arg) and + ret = outp.getEntryNode(fd) and + guard.dominates(ret.asInstruction().getBasicBlock()) + | + exists(boolean b | + onlyPossibleReturnOfBool(fd, outp, ret, b) and + p.isBoolean(b) + ) + or + onlyPossibleReturnOfNonNil(fd, outp, ret) and + p.isNonNil() + ) + } +} + +/** + * Holds if `ret` is a data-flow node whose value contributes to the output `res` of `fd`, + * and that node may have Boolean value `b`. + */ +private predicate possiblyReturnsBool(FuncDecl fd, FunctionOutput res, Node ret, Boolean b) { + ret = res.getEntryNode(fd) and + ret.getType().getUnderlyingType() instanceof BoolType and + not ret.getBoolValue() != b +} + +/** + * Holds if `ret` is the only data-flow node whose value contributes to the output `res` of `fd` + * that may have Boolean value `b`, since all the other output nodes have a Boolean value + * other than `b`. + */ +private predicate onlyPossibleReturnOfBool(FuncDecl fd, FunctionOutput res, Node ret, boolean b) { + possiblyReturnsBool(fd, res, ret, b) and + forall(Node otherRet | otherRet = res.getEntryNode(fd) and otherRet != ret | + otherRet.getBoolValue() != b + ) +} + +/** + * Holds if `ret` is a data-flow node whose value contributes to the output `res` of `fd`, + * and that node may evaluate to a value other than `nil`. + */ +private predicate possiblyReturnsNonNil(FuncDecl fd, FunctionOutput res, Node ret) { + ret = res.getEntryNode(fd) and + not ret.asExpr() = Builtin::nil().getAReference() +} + +/** + * Holds if `ret` is the only data-flow node whose value contributes to the output `res` of `fd` + * that may have a value other than `nil`, since all the other output nodes evaluate to `nil`. + */ +private predicate onlyPossibleReturnOfNonNil(FuncDecl fd, FunctionOutput res, Node ret) { + possiblyReturnsNonNil(fd, res, ret) and + forall(Node otherRet | otherRet = res.getEntryNode(fd) and otherRet != ret | + otherRet.asExpr() = Builtin::nil().getAReference() + ) +} diff --git a/ql/src/semmle/go/frameworks/Glog.qll b/ql/src/semmle/go/frameworks/Glog.qll new file mode 100644 index 00000000..d3fefb9a --- /dev/null +++ b/ql/src/semmle/go/frameworks/Glog.qll @@ -0,0 +1,27 @@ +/** Provides models of commonly used functions in the `github.com/golang/glog` package. */ + +import go + +module Glog { + private class GlogCall extends LoggerCall::Range, DataFlow::CallNode { + GlogCall() { + exists(string fn | + fn.regexpMatch("Error(|f|ln)") + or + fn.regexpMatch("Exit(|f|ln)") + or + fn.regexpMatch("Fatal(|f|ln)") + or + fn.regexpMatch("Info(|f|ln)") + or + fn.regexpMatch("Warning(|f|ln)") + | + this.getTarget().hasQualifiedName("github.com/golang/glog", fn) + or + this.getTarget().(Method).hasQualifiedName("github.com/golang/glog", "Verbose", fn) + ) + } + + override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() } + } +} diff --git a/ql/src/semmle/go/frameworks/HTTP.qll b/ql/src/semmle/go/frameworks/HTTP.qll new file mode 100644 index 00000000..40dfd80c --- /dev/null +++ b/ql/src/semmle/go/frameworks/HTTP.qll @@ -0,0 +1,149 @@ +/** + * Provides classes for working with HTTP-related concepts such as requests and responses. + */ + +import go + +private module StdlibHttp { + /** An access to an HTTP request field whose value may be controlled by an untrusted user. */ + private class UserControlledRequestField extends UntrustedFlowSource::Range, DataFlow::ExprNode { + override SelectorExpr expr; + + UserControlledRequestField() { + exists(Type req, Type baseType, string fieldName | + req.hasQualifiedName("net/http", "Request") and + baseType = expr.getBase().getType() and + fieldName = expr.getSelector().getName() and + (baseType = req or baseType = req.getPointerType()) and + (fieldName = "Body" or fieldName = "Form" or fieldName = "Header" or fieldName = "URL") + ) + } + } + + private class StdlibResponseWriter extends HTTP::ResponseWriter::Range { + StdlibResponseWriter() { this.getType().implements("net/http", "ResponseWriter") } + + /** Gets a header object that corresponds to this HTTP response. */ + DataFlow::MethodCallNode getAHeaderObject() { + result.getTarget().hasQualifiedName("net/http", _, "Header") and + this.getARead() = result.getReceiver() + } + } + + private class HeaderCall extends HTTP::HeaderWrite::Range, DataFlow::MethodCallNode { + HeaderCall() { + this.getTarget().hasQualifiedName("net/http", "Header", "Add") or + this.getTarget().hasQualifiedName("net/http", "Header", "Set") + } + + override DataFlow::Node getName() { result = this.getArgument(0) } + + override DataFlow::Node getValue() { result = this.getArgument(1) } + + override HTTP::ResponseWriter getResponseWriter() { + // find `v` in + // ``` + // header := v.Header() + // header.Add(...) + // ``` + result.(StdlibResponseWriter).getAHeaderObject().getASuccessor*() = this.getReceiver() + } + } + + private class MapWrite extends HTTP::HeaderWrite::Range, DataFlow::Node { + Write write; + DataFlow::Node index; + DataFlow::Node rhs; + + MapWrite() { + this.getType().hasQualifiedName("net/http", "Header") and + write.writesElement(this, index, rhs) + } + + override DataFlow::Node getName() { result = index } + + override DataFlow::Node getValue() { result = rhs } + + override HTTP::ResponseWriter getResponseWriter() { + // find `v` in + // ``` + // header := v.Header() + // header[...] = ... + // ``` + result.(StdlibResponseWriter).getAHeaderObject().getASuccessor*() = this + } + } + + private class ResponseWriteHeaderCall extends HTTP::HeaderWrite::Range, DataFlow::MethodCallNode { + ResponseWriteHeaderCall() { + this.getTarget().implements("net/http", "ResponseWriter", "WriteHeader") + } + + override string getHeaderName() { result = "status" } + + override predicate definesHeader(string header, string value) { + header = "status" and value = this.getValue().getIntValue().toString() + } + + override DataFlow::Node getName() { none() } + + override DataFlow::Node getValue() { result = this.getArgument(0) } + + override HTTP::ResponseWriter getResponseWriter() { result.getANode() = this.getReceiver() } + } + + private class RequestBody extends HTTP::RequestBody::Range, DataFlow::ExprNode { + RequestBody() { + exists(DataFlow::CallNode newRequestCall | + newRequestCall.getTarget().hasQualifiedName("net/http", "NewRequest") + | + this = newRequestCall.getArgument(2) + ) + or + exists(Write w, DataFlow::Node base, Field body, Type request | + w.writesField(base, body, this) and + request.hasQualifiedName("net/http", "Request") and + request.getPointerType() = base.getType().getUnderlyingType() and + body.getName() = "Body" + ) + } + } + + private class ResponseBody extends HTTP::ResponseBody::Range, DataFlow::ArgumentNode { + int arg; + + ResponseBody() { + exists(DataFlow::CallNode call | + call.getTarget().(Method).implements("net/http", "ResponseWriter", "Write") and + arg = 0 + or + ( + call.getTarget().hasQualifiedName("fmt", "Fprintf") + or + call.getTarget().hasQualifiedName("io", "WriteString") + ) and + call.getArgument(0).getType().hasQualifiedName("net/http", "ResponseWriter") and + arg >= 1 + | + this = call.getArgument(arg) + ) + } + + override HTTP::ResponseWriter getResponseWriter() { + // the response writer is the receiver of this call + result.getANode() = this.getCall().(DataFlow::MethodCallNode).getReceiver() + or + // the response writer is an argument to Fprintf or WriteString + arg >= 1 and + result.getANode() = this.getCall().getArgument(0) + } + } + + private class RedirectCall extends HTTP::Redirect::Range, DataFlow::CallNode { + RedirectCall() { this.getTarget().hasQualifiedName("net/http", "Redirect") } + + override DataFlow::Node getUrl() { result = this.getArgument(2) } + + override HTTP::ResponseWriter getResponseWriter() { result.getANode() = this.getArgument(0) } + } +} diff --git a/ql/src/semmle/go/frameworks/Logrus.qll b/ql/src/semmle/go/frameworks/Logrus.qll new file mode 100644 index 00000000..f69a58a0 --- /dev/null +++ b/ql/src/semmle/go/frameworks/Logrus.qll @@ -0,0 +1,60 @@ +/** Provides models of commonly used functions in the `github.com/sirupsen/logrus` package. */ + +import go + +module Logrus { + private string getAPkgName() { + result = "github.com/sirupsen/logrus" + or + result = "github.com/Sirupsen/logrus" + } + + bindingset[result] + private string getALogResultName() { + result.matches("Debug%") + or + result.matches("Error%") + or + result.matches("Fatal%") + or + result.matches("Info%") + or + result.matches("Panic%") + or + result.matches("Print%") + or + result.matches("Trace%") + or + result.matches("Warn%") + } + + private class LogCall extends LoggerCall::Range, DataFlow::CallNode { + LogCall() { this.getTarget().hasQualifiedName(getAPkgName(), getALogResultName()) } + + override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() } + } + + private class LogEntryCall extends LoggerCall::Range, DataFlow::MethodCallNode { + LogEntryCall() { + this.getTarget().(Method).hasQualifiedName(getAPkgName(), "Entry", getALogResultName()) + } + + override DataFlow::Node getAMessageComponent() { + result = this.getAnArgument() + or + exists(DataFlow::MethodCallNode addFieldCall, DataFlow::SsaNode entry | + entry.getAUse() = this.getReceiver() and + entry.getAUse() = addFieldCall.getReceiver() + | + addFieldCall.getCalleeName().regexpMatch("With(Context|Error|Fields?|Time)") and + result = addFieldCall.getAnArgument() + ) + or + exists(DataFlow::CallNode entryBuild | + entryBuild.getASuccessor*() = this.getReceiver() and + entryBuild.getCalleeName().regexpMatch("With(Context|Error|Fields?|Time)") and + result = entryBuild.getAnArgument() + ) + } + } +} diff --git a/ql/src/semmle/go/frameworks/SQL.qll b/ql/src/semmle/go/frameworks/SQL.qll new file mode 100644 index 00000000..172d1590 --- /dev/null +++ b/ql/src/semmle/go/frameworks/SQL.qll @@ -0,0 +1,71 @@ +/** + * Provides classes for working with SQL-related concepts such as queries. + */ + +import go + +module SQL { + /** + * A data-flow node whose string value is interpreted as (part of) a SQL query. + * + * Extends this class to refine existing API models. If you want to model new APIs, + * extend `SQL::QueryString::Range` instead. + */ + class QueryString extends DataFlow::Node { + QueryString::Range self; + + QueryString() { this = self } + } + + module QueryString { + /** + * A data-flow node whose string value is interpreted as (part of) a SQL query. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `SQL::QueryString` instead. + */ + abstract class Range extends DataFlow::Node { } + + /** A query string used in an API function of the standard `database/sql` package. */ + private class StandardQueryString extends Range { + StandardQueryString() { + exists(Method meth, string base, string m, int n | + meth.hasQualifiedName("database/sql", "DB", m) and + this = meth.getACall().getArgument(n) + | + (base = "Exec" or base = "Prepare" or base = "Query" or base = "QueryRow") and + ( + m = base and n = 0 + or + m = base + "Context" and n = 1 + ) + ) + } + } + + /** + * An argument to an API of the squirrel library that is directly interpreted as SQL without + * taking syntactic structure into account. + */ + private class SquirrelQueryString extends Range { + SquirrelQueryString() { + exists(Function fn | + exists(string sq | + sq = "github.com/Masterminds/squirrel" or + sq = "github.com/lann/squirrel" | + // first argument to `squirrel.Expr` + fn.hasQualifiedName(sq, "Expr") + or + // first argument to the `Prefix` or `Suffix` method of one of the `*Builder` classes + exists(string builder | builder.matches("%Builder") | + fn.(Method).hasQualifiedName(sq, builder, "Prefix") or + fn.(Method).hasQualifiedName(sq, builder, "Suffix") + ) + ) and + this = fn.getACall().getArgument(0) and + this.getType().getUnderlyingType() instanceof StringType + ) + } + } + } +} diff --git a/ql/src/semmle/go/frameworks/Stdlib.qll b/ql/src/semmle/go/frameworks/Stdlib.qll new file mode 100644 index 00000000..eafbd375 --- /dev/null +++ b/ql/src/semmle/go/frameworks/Stdlib.qll @@ -0,0 +1,533 @@ +/** + * Provides classes modeling security-relevant aspects of the standard libraries. + */ + +import go + +/** A `String()` method. */ +class StringMethod extends TaintTracking::FunctionModel, Method { + StringMethod() { getName() = "String" and getNumParameter() = 0 } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isReceiver() and outp.isResult() + } +} + +/** Provides models of commonly used functions in the `path/filepath` package. */ +module PathFilePath { + /** A path-manipulating function in the `path/filepath` package. */ + private class PathManipulatingFunction extends TaintTracking::FunctionModel { + PathManipulatingFunction() { + exists(string fn | hasQualifiedName("path/filepath", fn) | + fn = "Abs" or + fn = "Base" or + fn = "Clean" or + fn = "Dir" or + fn = "EvalSymlinks" or + fn = "Ext" or + fn = "FromSlash" or + fn = "Glob" or + fn = "Join" or + fn = "Rel" or + fn = "Split" or + fn = "SplitList" or + fn = "ToSlash" or + fn = "VolumeName" + ) + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(_) and + (outp.isResult() or outp.isResult(_)) + } + } +} + +/** Provides models of commonly used functions in the `fmt` package. */ +module Fmt { + /** The `Sprint` function or one of its variants. */ + class Sprinter extends TaintTracking::FunctionModel { + Sprinter() { + exists(string sprint | sprint.matches("Sprint%") | hasQualifiedName("fmt", sprint)) + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(_) and outp.isResult() + } + } + + private class PrintCall extends LoggerCall::Range, DataFlow::CallNode { + PrintCall() { + exists(string fn | + fn = "Print%" + or + fn = "Fprint%" + | + this.getTarget().hasQualifiedName("fmt", fn) + ) + } + + override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() } + } +} + +/** Provides models of commonly used functions in the `io/ioutil` package. */ +module IoUtil { + private class IoUtilFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode { + IoUtilFileSystemAccess() { + exists(string fn | getTarget().hasQualifiedName("io/ioutil", fn) | + fn = "ReadDir" or + fn = "ReadFile" or + fn = "WriteFile" + ) + } + + override DataFlow::Node getAPathArgument() { result = getArgument(0) } + } +} + +/** Provides models of commonly used functions in the `os` package. */ +module OS { + /** + * A call to a function in `os` that accesses the file system. + */ + private class OsFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode { + int pathidx; + + OsFileSystemAccess() { + exists(string fn | getTarget().hasQualifiedName("os", fn) | + fn = "Chdir" and pathidx = 0 + or + fn = "Chmod" and pathidx = 0 + or + fn = "Chown" and pathidx = 0 + or + fn = "Chtimes" and pathidx = 0 + or + fn = "Create" and pathidx = 0 + or + fn = "Lchown" and pathidx = 0 + or + fn = "Link" and pathidx in [0 .. 1] + or + fn = "Lstat" and pathidx = 0 + or + fn = "Mkdir" and pathidx = 0 + or + fn = "MkdirAll" and pathidx = 0 + or + fn = "NewFile" and pathidx = 1 + or + fn = "Open" and pathidx = 0 + or + fn = "OpenFile" and pathidx = 0 + or + fn = "Readlink" and pathidx = 0 + or + fn = "Remove" and pathidx = 0 + or + fn = "RemoveAll" and pathidx = 0 + or + fn = "Rename" and pathidx in [0 .. 1] + or + fn = "Stat" and pathidx = 0 + or + fn = "Symlink" and pathidx in [0 .. 1] + or + fn = "Truncate" and pathidx = 0 + ) + } + + override DataFlow::Node getAPathArgument() { result = getArgument(pathidx) } + } + + /** The `Expand` function. */ + class Expand extends TaintTracking::FunctionModel { + Expand() { hasQualifiedName("os", "Expand") } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and outp.isResult() + } + } + + /** The `ExpandEnv` function. */ + class ExpandEnv extends TaintTracking::FunctionModel { + ExpandEnv() { hasQualifiedName("os", "ExpandEnv") } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and outp.isResult() + } + } +} + +/** Provides models of commonly used functions in the `path` package. */ +module Path { + /** A path-manipulating function in the `path` package. */ + private class PathManipulatingFunction extends TaintTracking::FunctionModel { + PathManipulatingFunction() { + exists(string fn | hasQualifiedName("path", fn) | + fn = "Base" or + fn = "Clean" or + fn = "Dir" or + fn = "Ext" or + fn = "Join" or + fn = "Split" + ) + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(_) and + (outp.isResult() or outp.isResult(_)) + } + } +} + +/** Provides models of commonly used functions in the `strings` package. */ +module Strings { + /** The `Join` function. */ + class Join extends TaintTracking::FunctionModel { + Join() { hasQualifiedName("strings", "Join") } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter([0 .. 1]) and outp.isResult() + } + } + + /** The `Repeat` function. */ + class Repeat extends TaintTracking::FunctionModel { + Repeat() { hasQualifiedName("strings", "Repeat") } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and outp.isResult() + } + } + + /** The `Replace` or `ReplaceAll` function. */ + class Replacer extends TaintTracking::FunctionModel { + Replacer() { + hasQualifiedName("strings", "Replace") or hasQualifiedName("strings", "ReplaceAll") + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + (inp.isParameter(0) or inp.isParameter(2)) and + outp.isResult() + } + } + + /** The `Split` function or one of its variants. */ + class Splitter extends TaintTracking::FunctionModel { + Splitter() { + exists(string split | split.matches("Split%") | hasQualifiedName("strings", split)) + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and outp.isResult() + } + } + + /** One of the case-converting functions in the `strings` package. */ + class CaseConverter extends TaintTracking::FunctionModel { + CaseConverter() { + exists(string conv | conv.matches("To%") | hasQualifiedName("strings", conv)) + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(getNumParameter() - 1) and outp.isResult() + } + } + + /** The `Trim` function or one of its variants. */ + class Trimmer extends TaintTracking::FunctionModel { + Trimmer() { exists(string split | split.matches("Trim%") | hasQualifiedName("strings", split)) } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and outp.isResult() + } + } +} + +/** Provides models of commonly used functions in the `text/template` package. */ +module Template { + private class TemplateEscape extends EscapeFunction::Range { + string kind; + + TemplateEscape() { + exists(string fn | + fn.matches("HTMLEscape%") and kind = "html" + or + fn.matches("JSEscape%") and kind = "js" + or + fn.matches("URLQueryEscape%") and kind = "url" + | + this.hasQualifiedName("text/template", fn) + or + this.hasQualifiedName("html/template", fn) + ) + } + + override string kind() { result = kind } + } + + private class TextTemplateInstantiation extends TemplateInstantiation::Range, + DataFlow::MethodCallNode { + int dataArg; + + TextTemplateInstantiation() { + exists(string m | getTarget().hasQualifiedName("text/template", "Template", m) | + m = "Execute" and + dataArg = 1 + or + m = "ExecuteTemplate" and + dataArg = 2 + ) + } + + override DataFlow::Node getTemplateArgument() { result = this.getReceiver() } + + override DataFlow::Node getADataArgument() { result = this.getArgument(dataArg) } + } +} + +/** Provides models of commonly used functions in the `net/url` package. */ +module URL { + /** The `PathEscape` or `QueryEscape` function. */ + class Escaper extends TaintTracking::FunctionModel { + Escaper() { + hasQualifiedName("net/url", "PathEscape") or hasQualifiedName("net/url", "QueryEscape") + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and outp.isResult() + } + } + + /** The `PathUnescape` or `QueryUnescape` function. */ + class Unescaper extends TaintTracking::FunctionModel { + Unescaper() { + hasQualifiedName("net/url", "PathUnescape") or hasQualifiedName("net/url", "QueryUnescape") + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and outp.isResult(0) + } + } + + /** The `Parse`, `ParseQuery` or `ParseRequestURI` function, or the `URL.Parse` method. */ + class Parser extends TaintTracking::FunctionModel { + Parser() { + hasQualifiedName("net/url", "Parse") or + this.(Method).hasQualifiedName("net/url", "URL", "Parse") or + hasQualifiedName("net/url", "ParseQuery") or + hasQualifiedName("net/url", "ParseRequestURI") + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and + outp.isResult(0) + or + this instanceof Method and + inp.isReceiver() and + outp.isResult(0) + } + } + + /** A method that returns a part of a URL. */ + class UrlGetter extends TaintTracking::FunctionModel, Method { + UrlGetter() { + exists(string m | hasQualifiedName("net/url", "URL", m) | + m = "EscapedPath" or + m = "Hostname" or + m = "Port" or + m = "Query" or + m = "RequestURI" + ) + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isReceiver() and outp.isResult() + } + } + + /** The method `URL.MarshalBinary`. */ + class UrlMarshalBinary extends TaintTracking::FunctionModel, Method { + UrlMarshalBinary() { hasQualifiedName("net/url", "URL", "MarshalBinary") } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isReceiver() and outp.isResult(0) + } + } + + /** The method `URL.ResolveReference`. */ + class UrlResolveReference extends TaintTracking::FunctionModel, Method { + UrlResolveReference() { hasQualifiedName("net/url", "URL", "ResolveReference") } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + (inp.isReceiver() or inp.isParameter(0)) and + outp.isResult() + } + } + + /** The function `User` or `UserPassword`. */ + class UserinfoConstructor extends TaintTracking::FunctionModel { + UserinfoConstructor() { + hasQualifiedName("net/url", "User") or + hasQualifiedName("net/url", "UserPassword") + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(_) and outp.isResult() + } + } + + /** A method that returns a part of a Userinfo struct. */ + class UserinfoGetter extends TaintTracking::FunctionModel, Method { + UserinfoGetter() { + exists(string m | hasQualifiedName("net/url", "Userinfo", m) | + m = "Password" or + m = "Username" + ) + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isReceiver() and + if getName() = "Password" then outp.isResult(0) else outp.isResult() + } + } + + /** A method that returns all or part of a Values map. */ + class ValuesGetter extends TaintTracking::FunctionModel, Method { + ValuesGetter() { + exists(string m | hasQualifiedName("net/url", "Values", m) | + m = "Encode" or + m = "Get" + ) + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isReceiver() and outp.isResult() + } + } +} + +module Regexp { + private class Pattern extends RegexpPattern::Range, DataFlow::ArgumentNode { + string fnName; + + Pattern() { + exists(Function fn | fnName.matches("Match%") or fnName.matches("%Compile%") | + fn.hasQualifiedName("regexp", fnName) and + this = fn.getACall().getArgument(0) + ) + } + + override DataFlow::Node getAParse() { result = this.getCall() } + + override string getPattern() { result = this.asExpr().getStringValue() } + + override DataFlow::Node getAUse() { + fnName.matches("MustCompile%") and + result = this.getCall().getASuccessor*() + or + fnName.matches("Compile%") and + result = this.getCall().getResult(0).getASuccessor*() + or + result = this + } + } + + private class MatchFunction extends RegexpMatchFunction::Range, Function { + MatchFunction() { + exists(string fn | fn.matches("Match%") | this.hasQualifiedName("regexp", fn)) + } + + override FunctionInput getRegexpArg() { result.isParameter(0) } + + override FunctionInput getValue() { result.isParameter(1) } + + override FunctionOutput getResult() { result.isResult(0) } + } + + private class MatchMethod extends RegexpMatchFunction::Range, Method { + MatchMethod() { + exists(string fn | fn.matches("Match%") | this.hasQualifiedName("regexp", "Regexp", fn)) + } + + override FunctionInput getRegexpArg() { result.isReceiver() } + + override FunctionInput getValue() { result.isParameter(0) } + + override FunctionOutput getResult() { result.isResult() } + } + + private class ReplaceFunction extends RegexpReplaceFunction::Range, Method { + ReplaceFunction() { + exists(string fn | fn.matches("ReplaceAll%") | this.hasQualifiedName("regexp", "Regexp", fn)) + } + + override FunctionInput getRegexpArg() { result.isReceiver() } + + override FunctionInput getSource() { result.isParameter(0) } + + override FunctionOutput getResult() { result.isResult() } + } +} + +/** Provides models of commonly used functions in the `log` package. */ +module Log { + private class LogCall extends LoggerCall::Range, DataFlow::CallNode { + LogCall() { + exists(string fn | + fn.matches("Fatal%") + or + fn.matches("Panic%") + or + fn.matches("Print%") + | + this.getTarget().hasQualifiedName("log", fn) + or + this.getTarget().(Method).hasQualifiedName("log", "Logger", fn) + ) + } + + override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() } + } +} + +/** Provides models of some functions in the `encoding/json` package. */ +module EncodingJson { + private class MarshalFunction extends TaintTracking::FunctionModel { + MarshalFunction() { + this.hasQualifiedName("encoding/json", "Marshal") or + this.hasQualifiedName("encoding/json", "MarshalIndent") + } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and + outp.isResult(0) + } + } +} + +/** Provides models of some functions in the `encoding/hex` package. */ +module EncodingHex { + private class DecodeStringFunction extends TaintTracking::FunctionModel { + DecodeStringFunction() { this.hasQualifiedName("encoding/hex", "DecodeString") } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(0) and + outp.isResult(0) + } + } +} + +/** Provides models of some functions in the `crypto/cipher` package. */ +module CryptoCipher { + private class AeadOpenFunction extends TaintTracking::FunctionModel, Method { + AeadOpenFunction() { this.hasQualifiedName("crypto/cipher", "AEAD", "Open") } + + override predicate hasTaintFlow(DataFlow::FunctionInput inp, DataFlow::FunctionOutput outp) { + inp.isParameter(2) and + outp.isResult(0) + } + } +} diff --git a/ql/src/semmle/go/frameworks/SystemCommandExecutors.qll b/ql/src/semmle/go/frameworks/SystemCommandExecutors.qll new file mode 100644 index 00000000..489f7295 --- /dev/null +++ b/ql/src/semmle/go/frameworks/SystemCommandExecutors.qll @@ -0,0 +1,35 @@ +/** + * Provides concrete classes for data-flow nodes that execute an + * operating system command, for instance by spawning a new process. + */ + +import go + +private class SystemCommandExecutors extends SystemCommandExecution::Range, DataFlow::CallNode { + int cmdArg; + + SystemCommandExecutors() { + exists(string pkg, string name | this.getTarget().hasQualifiedName(pkg, name) | + pkg = "os" and name = "StartProcess" and cmdArg = 0 + or + // assume that if a `Cmd` is instantiated it will be run + pkg = "os/exec" and name = "Command" and cmdArg = 0 + or + pkg = "os/exec" and name = "CommandContext" and cmdArg = 1 + ) + } + + override DataFlow::Node getCommandName() { result = this.getArgument(cmdArg) } +} + +/** + * A call to the `Command` function from the [go-sh](https://github.com/codeskyblue/go-sh) + * package, viewed as a system-command execution. + */ +private class GoShCommandExecution extends SystemCommandExecution::Range, DataFlow::CallNode { + GoShCommandExecution() { + getTarget().hasQualifiedName("github.com/codeskyblue/go-sh", "Command") + } + + override DataFlow::Node getCommandName() { result = this.getArgument(0) } +} diff --git a/ql/src/semmle/go/security/CleartextLogging.qll b/ql/src/semmle/go/security/CleartextLogging.qll new file mode 100644 index 00000000..a93ac5f2 --- /dev/null +++ b/ql/src/semmle/go/security/CleartextLogging.qll @@ -0,0 +1,52 @@ +/** + * Provides a data-flow tracking configuration for reasoning about + * clear-text logging of sensitive information. + * + * Note, for performance reasons: only import this file if + * `CleartextLogging::Configuration` is needed, otherwise + * `CleartextLoggingCustomizations` should be imported instead. + */ + +import go + +module CleartextLogging { + import CleartextLoggingCustomizations::CleartextLogging + + /** + * A data-flow tracking configuration for clear-text logging of sensitive information. + * + * This configuration identifies flows from `Source`s, which are sources of + * sensitive data, to `Sink`s, which is an abstract class representing all + * the places sensitive data may be stored in cleartext. Additional sources or sinks can be + * added either by extending the relevant class, or by subclassing this configuration itself, + * and amending the sources and sinks. + */ + class Configuration extends DataFlow::Configuration { + Configuration() { this = "CleartextLogging" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier } + + override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node trg) { + // A taint propagating data-flow edge through structs: a tainted write taints the entire struct. + exists(Write write | write.writesField(trg.getASuccessor*(), _, src)) + or + trg.(DataFlow::BinaryOperationNode).getOperator() = "+" and + src = trg.(DataFlow::BinaryOperationNode).getAnOperand() + or + // Allow flow through functions that are considered for taint flow. + exists( + TaintTracking::FunctionModel m, DataFlow::CallNode c, DataFlow::FunctionInput inp, + DataFlow::FunctionOutput outp + | + c = m.getACall() and + m.hasTaintFlow(inp, outp) and + src = inp.getNode(c) and + trg = outp.getNode(c) + ) + } + } +} diff --git a/ql/src/semmle/go/security/CleartextLoggingCustomizations.qll b/ql/src/semmle/go/security/CleartextLoggingCustomizations.qll new file mode 100644 index 00000000..39106dca --- /dev/null +++ b/ql/src/semmle/go/security/CleartextLoggingCustomizations.qll @@ -0,0 +1,142 @@ +/** + * Provides default sources, sinks, and sanitizers for reasoning about + * clear-text logging of sensitive information, as well as extension + * points for adding your own. + */ + +import go +private import semmle.go.security.SensitiveActions::HeuristicNames + +module CleartextLogging { + /** + * A data-flow source for clear-text logging of sensitive information. + */ + abstract class Source extends DataFlow::Node { + /** Gets a string that describes the type of this data-flow source. */ + abstract string describe(); + } + + /** + * A data-flow sink for clear-text logging of sensitive information. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A barrier for clear-text logging of sensitive information. + */ + abstract class Barrier extends DataFlow::Node { } + + /** + * An argument to a logging mechanism. + */ + class LoggerSink extends Sink { + LoggerSink() { this = any(LoggerCall log).getAMessageComponent() } + } + + /** + * A data-flow node that does not contain a clear-text password, according to its syntactic name. + */ + private class NameGuidedNonCleartextPassword extends NonCleartextPassword { + NameGuidedNonCleartextPassword() { + exists(string name | + name.regexpMatch(notSensitive()) + or + name.regexpMatch("(?i).*err(or)?.*") + | + this.asExpr().(Ident).getName() = name + or + this.(DataFlow::FieldReadNode).getFieldName() = name + or + this.(DataFlow::CallNode).getCalleeName() = name + ) + or + // avoid i18n strings + this + .(DataFlow::FieldReadNode) + .getBase() + .asExpr() + .(Ident) + .getName() + .regexpMatch("(?is).*(messages|strings).*") + } + } + + /** + * A data-flow node that receives flow that is not a clear-text password. + */ + private class NonCleartextPasswordFlow extends NonCleartextPassword { + NonCleartextPasswordFlow() { this = any(NonCleartextPassword other).getASuccessor*() } + } + + /** + * A call that might obfuscate a password, for example through hashing. + */ + private class ObfuscatorCall extends Barrier, DataFlow::CallNode { + ObfuscatorCall() { this.getCalleeName().regexpMatch(notSensitive()) } + } + + /** + * A data-flow node that does not contain a clear-text password. + */ + abstract private class NonCleartextPassword extends DataFlow::Node { } + + /** + * An struct with a field that may contain password information. + * + * This is a source since `log.Print(obj)` will often show the fields of `obj`. + */ + private class StructPasswordFieldSource extends DataFlow::Node, Source { + string name; + + StructPasswordFieldSource() { + exists(Write write, Field f, DataFlow::Node rhs | + write.writesField(this.getASuccessor*(), f, rhs) + | + name = f.getName() and + name.regexpMatch(maybePassword()) and + not name.regexpMatch(notSensitive()) and + // avoid safe values assigned to presumably unsafe names + not rhs instanceof NonCleartextPassword + ) + } + + override string describe() { result = "an access to " + name } + } + + /** An access to a variable or property that might contain a password. */ + private class ReadPasswordSource extends DataFlow::Node, Source { + string name; + + ReadPasswordSource() { + // avoid safe values assigned to presumably unsafe names + not this instanceof NonCleartextPassword and + name.regexpMatch(maybePassword()) and + ( + this.asExpr().(Ident).getName() = name + or + exists(DataFlow::FieldReadNode fn | + fn = this and + fn.getFieldName() = name and + // avoid safe values assigned to presumably unsafe names + not exists(Write write, NonCleartextPassword ncp | + write.writesField(fn.getBase().getAPredecessor*(), fn.getField(), ncp) + ) + ) + ) + } + + override string describe() { result = "an access to " + name } + } + + /** A call that might return a password. */ + private class CallPasswordSource extends DataFlow::CallNode, Source { + string name; + + CallPasswordSource() { + name = getCalleeName() and + name.regexpMatch("(?is)getPassword") + } + + override string describe() { result = "a call to " + name } + } +} diff --git a/ql/src/semmle/go/security/CommandInjection.qll b/ql/src/semmle/go/security/CommandInjection.qll new file mode 100644 index 00000000..c1b16bde --- /dev/null +++ b/ql/src/semmle/go/security/CommandInjection.qll @@ -0,0 +1,32 @@ +/** + * Provides a taint tracking configuration for reasoning about command + * injection vulnerabilities + * + * Note, for performance reasons: only import this file if + * `CommandInjection::Configuration` is needed, otherwise + * `CommandInjectionCustomizations` should be imported instead. + */ + +import go + +module CommandInjection { + import CommandInjectionCustomizations::CommandInjection + + /** + * A taint-tracking configuration for reasoning about command-injection vulnerabilities. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "CommandInjection" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } + + override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { guard instanceof SanitizerGuard } + } +} diff --git a/ql/src/semmle/go/security/CommandInjectionCustomizations.qll b/ql/src/semmle/go/security/CommandInjectionCustomizations.qll new file mode 100644 index 00000000..3db4f333 --- /dev/null +++ b/ql/src/semmle/go/security/CommandInjectionCustomizations.qll @@ -0,0 +1,39 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * command-injection vulnerabilities, as well as extension points for + * adding your own. + */ + +import go + +module CommandInjection { + /** + * A data flow source for command-injection vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for command-injection vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for command-injection vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A sanitizer guard for command-injection vulnerabilities. + */ + abstract class SanitizerGuard extends DataFlow::BarrierGuard { } + + /** A source of untrusted data, considered as a taint source for command injection. */ + class UntrustedFlowAsSource extends Source { + UntrustedFlowAsSource() { this instanceof UntrustedFlowSource } + } + + /** A command name, considered as a taint sink for command injection. */ + class CommandNameAsSink extends Sink { + CommandNameAsSink() { this = any(SystemCommandExecution exec).getCommandName() } + } +} diff --git a/ql/src/semmle/go/security/FlowSources.qll b/ql/src/semmle/go/security/FlowSources.qll new file mode 100644 index 00000000..56db2372 --- /dev/null +++ b/ql/src/semmle/go/security/FlowSources.qll @@ -0,0 +1,27 @@ +/** + * Provides classes representing various flow sources for taint tracking. + */ + +import go + +/** + * A source of data that is controlled by an untrusted user. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `UntrustedFlowSource::Range` instead. + */ +class UntrustedFlowSource extends DataFlow::Node { + UntrustedFlowSource::Range self; + + UntrustedFlowSource() { this = self } +} + +module UntrustedFlowSource { + /** + * A source of data that is controlled by an untrusted user. + * + * Extend this class to model new APIs. If you want to refine existing API models. If you want to model new APIs, + * extend `UntrustedFlowSource` instead. + */ + abstract class Range extends DataFlow::Node { } +} diff --git a/ql/src/semmle/go/security/OpenUrlRedirect.qll b/ql/src/semmle/go/security/OpenUrlRedirect.qll new file mode 100644 index 00000000..0447882f --- /dev/null +++ b/ql/src/semmle/go/security/OpenUrlRedirect.qll @@ -0,0 +1,58 @@ +/** + * Provides a taint-tracking configuration for reasoning about + * unvalidated URL redirection problems on the server side. + * + * Note, for performance reasons: only import this file if + * `OpenUrlRedirect::Configuration` is needed, otherwise + * `OpenUrlRedirectCustomizations` should be imported instead. + */ + +import go +import UrlConcatenation + +module OpenUrlRedirect { + import OpenUrlRedirectCustomizations::OpenUrlRedirect + + /** + * A taint-tracking configuration for reasoning about unvalidated URL redirections. + */ + class Configuration extends DataFlow::Configuration { + Configuration() { this = "OpenUrlRedirect" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier } + + override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) { + // A write to URL.Host + exists(Write write, Field f, DataFlow::SsaNode var | + write.writesField(var.getAUse(), f, pred) and + succ = var.getAUse() and + write.getASuccessor+() = succ.asInstruction() and + f.getName() = "Host" and + var.getType().hasQualifiedName("net/url", "URL") + ) + or + StringConcatenation::taintStep(pred, succ) + or + // Allow flow through functions that are considered for taint flow. + exists( + TaintTracking::FunctionModel m, DataFlow::CallNode c, DataFlow::FunctionInput inp, + DataFlow::FunctionOutput outp + | + c = m.getACall() and + m.hasTaintFlow(inp, outp) and + pred = inp.getNode(c) and + succ = outp.getNode(c) + ) + } + + override predicate isBarrierOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) } + + override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { + guard instanceof BarrierGuard + } + } +} diff --git a/ql/src/semmle/go/security/OpenUrlRedirectCustomizations.qll b/ql/src/semmle/go/security/OpenUrlRedirectCustomizations.qll new file mode 100644 index 00000000..5eb463f1 --- /dev/null +++ b/ql/src/semmle/go/security/OpenUrlRedirectCustomizations.qll @@ -0,0 +1,113 @@ +/** + * Provides default sources, sinks and sanitisers for reasoning about + * unvalidated URL redirection problems, as well as extension points + * for adding your own. + */ + +import go +import UrlConcatenation + +module OpenUrlRedirect { + /** + * A data flow source for unvalidated URL redirect vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for unvalidated URL redirect vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A barrier for unvalidated URL redirect vulnerabilities. + */ + abstract class Barrier extends DataFlow::Node { } + + /** + * A barrier guard for unvalidated URL redirect vulnerabilities. + */ + abstract class BarrierGuard extends DataFlow::BarrierGuard { } + + /** + * A source of third-party user input, considered as a flow source for URL redirects. + */ + class UntrustedFlowAsSource extends Source, UntrustedFlowSource { } + + /** + * An HTTP redirect, considered as a sink for `Configuration`. + */ + class RedirectSink extends Sink, DataFlow::Node { + RedirectSink() { this = any(HTTP::Redirect redir).getUrl() } + } + + /** + * A definition of the HTTP "Location" header, considered as a sink for + * `Configuration`. + */ + class LocationHeaderSink extends Sink, DataFlow::Node { + LocationHeaderSink() { + exists(HTTP::HeaderWrite hw | hw.getHeaderName() = "location" | this = hw.getValue()) + } + } + + /** + * A call to a function called `isLocalUrl` or similar, which is + * considered a barrier for purposes of URL redirection. + */ + class LocalUrlBarrierGuard extends BarrierGuard, DataFlow::CallNode { + LocalUrlBarrierGuard() { this.getCalleeName().regexpMatch("(?i)(is_?)?local_?url") } + + override predicate checks(Expr e, boolean outcome) { + // `isLocalUrl(e)` is a barrier for `e` if it evaluates to `true` + getAnArgument().asExpr() = e and + outcome = true + } + } + + /** + * A check against a constant value, considered a barrier for redirection. + */ + class EqualityTestGuard extends BarrierGuard, DataFlow::EqualityTestNode { + DataFlow::Node url; + + EqualityTestGuard() { + exists(this.getAnOperand().getStringValue()) and + ( + url = this.getAnOperand() + or + exists(DataFlow::MethodCallNode mc | mc = this.getAnOperand() | + mc.getTarget().getName() = "Hostname" and + url = mc.getReceiver() + ) + ) + } + + override predicate checks(Expr e, boolean outcome) { + e = url.asExpr() and this.eq(outcome, _, _) + } + } + + /** + * A call to a regexp match function, considered as a barrier guard for unvalidated URLs. + * + * This is overapproximate: we do not attempt to reason about the correctness of the regexp. + */ + class RegexpCheck extends BarrierGuard { + DataFlow::CallNode call; + + RegexpCheck() { + exists(string fn | fn.matches("Match%") | + call.getTarget().hasQualifiedName("regexp", fn) and + this = DataFlow::extractTupleElement(call, 0).getASuccessor*() + or + call.getTarget().(Method).hasQualifiedName("regexp", "Regexp", fn) and + this = call.getASuccessor*() + ) + } + + override predicate checks(Expr e, boolean branch) { + e = call.getAnArgument().asExpr() and + (branch = false or branch = true) + } + } +} diff --git a/ql/src/semmle/go/security/ReflectedXss.qll b/ql/src/semmle/go/security/ReflectedXss.qll new file mode 100644 index 00000000..0e84a30a --- /dev/null +++ b/ql/src/semmle/go/security/ReflectedXss.qll @@ -0,0 +1,28 @@ +/** + * Provides a taint-tracking configuration for reasoning about reflected + * cross-site scripting vulnerabilities. + */ + +import go + +module ReflectedXss { + import ReflectedXssCustomizations::ReflectedXss + + /** + * A taint-tracking configuration for reasoning about XSS. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "ReflectedXss" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } + + override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { guard instanceof SanitizerGuard } + } +} diff --git a/ql/src/semmle/go/security/ReflectedXssCustomizations.qll b/ql/src/semmle/go/security/ReflectedXssCustomizations.qll new file mode 100644 index 00000000..08d644ed --- /dev/null +++ b/ql/src/semmle/go/security/ReflectedXssCustomizations.qll @@ -0,0 +1,82 @@ +/** + * Provides classes and predicates used by the XSS queries. + */ + +import go + +/** Provides classes and predicates for the reflected XSS query. */ +module ReflectedXss { + /** A data flow source for reflected XSS vulnerabilities. */ + abstract class Source extends DataFlow::Node { } + + /** A data flow sink for reflected XSS vulnerabilities. */ + abstract class Sink extends DataFlow::Node { + /** + * Gets the kind of vulnerability to report in the alert message. + * + * Defaults to `Cross-site scripting`, but may be overriden for sinks + * that do not allow script injection, but injection of other undesirable HTML elements. + */ + string getVulnerabilityKind() { result = "Cross-site scripting" } + } + + /** A sanitizer for reflected XSS vulnerabilities. */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A sanitizer guard for reflected XSS vulnerabilities. + */ + abstract class SanitizerGuard extends DataFlow::BarrierGuard { } + + /** + * An expression that is sent as part of an HTTP response body, considered as an + * XSS sink. + * + * We exclude cases where the route handler sets either an unknown content type or + * a content type that does not (case-insensitively) contain the string "html". This + * is to prevent us from flagging plain-text or JSON responses as vulnerable. + */ + class HttpResponseBodySink extends Sink, HTTP::ResponseBody { + HttpResponseBodySink() { not nonHtmlContentType(this.getResponseWriter()) } + } + + /** + * Holds if `h` may send a response with a content type other than HTML. + */ + private predicate nonHtmlContentType(HTTP::ResponseWriter rw) { + exists(HTTP::HeaderWrite hw | + hw = rw.getAHeaderWrite() and hw.definesHeader("content-type", _) + | + not exists(string tp | hw.definesHeader("content-type", tp) | tp.regexpMatch("(?i).*html.*")) + ) + } + + /** + * An expression that is sent as part of an HTTP header, considered as an XSS sink. + */ + class HttpResponseHeaderSink extends Sink, HTTP::Header { } + + /** + * A third-party controllable input, considered as a flow source for reflected XSS. + */ + class UntrustedFlowAsSource extends Source, UntrustedFlowSource { } + + /** + * A regexp replacement involving an HTML meta-character, or a call to an escape + * function, viewed as a sanitizer for XSS vulnerabilities. + * + * The XSS queries do not attempt to reason about correctness or completeness of sanitizers, + * so any such call stops taint propagation. + */ + class MetacharEscapeSanitizer extends Sanitizer, DataFlow::CallNode { + MetacharEscapeSanitizer() { + exists(Function f | f = this.getCall().getTarget() | + f.(RegexpReplaceFunction).getRegexp(this).getPattern().regexpMatch(".*['\"&<>].*") + or + f instanceof HtmlEscapeFunction + or + f instanceof JsEscapeFunction + ) + } + } +} diff --git a/ql/src/semmle/go/security/SensitiveActions.qll b/ql/src/semmle/go/security/SensitiveActions.qll new file mode 100644 index 00000000..87a7bc82 --- /dev/null +++ b/ql/src/semmle/go/security/SensitiveActions.qll @@ -0,0 +1,250 @@ +/** + * Provides classes and predicates for identifying sensitive data and methods for security. + * + * 'Sensitive' data in general is anything that should not be sent around in unencrypted form. This + * library tries to guess where sensitive data may either be stored in a variable or produced by a + * method. + * + * In addition, there are methods that ought not to be executed or not in a fashion that the user + * can control. This includes authorization methods such as logins, and sending of data, etc. + */ + +import go + +/** + * Provides heuristics for identifying names related to sensitive information. + * + * INTERNAL: Do not use directly. + */ +module HeuristicNames { + /** + * Gets a regular expression that identifies strings that may indicate the presence of secret + * or trusted data. + */ + string maybeSecret() { result = "(?is).*((? + + go-tests + + + + + + + com.semmle.plugin.qdt.core.qlnature + + diff --git a/ql/test/.qlpath b/ql/test/.qlpath new file mode 100644 index 00000000..ac27c763 --- /dev/null +++ b/ql/test/.qlpath @@ -0,0 +1,10 @@ + + + + /go-queries + + /go-queries/go.dbscheme + + go + + diff --git a/ql/test/extractor-tests/go1.13/literals.expected b/ql/test/extractor-tests/go1.13/literals.expected new file mode 100644 index 00000000..6da637c8 --- /dev/null +++ b/ql/test/extractor-tests/go1.13/literals.expected @@ -0,0 +1,9 @@ +| tst.go:4:7:4:12 | 0b1011 | 11 | 11 | +| tst.go:5:7:5:12 | 0B1011 | 11 | 11 | +| tst.go:6:7:6:11 | 0o660 | 432 | 432 | +| tst.go:7:7:7:11 | 0O660 | 432 | 432 | +| tst.go:8:7:8:16 | 0x1.p-1021 | 0x1.p-1021 | 1/22471164185778948846616314884862809170224712236778832159178760144716584475687620391588559665300942002640014234983924169707348721101802077811605928829934265547220986678108185659537777450155761764931635369010625721104768835292807860184239138817603404645418813835573287279993405742309964538104419541203028017152 | +| tst.go:9:7:9:16 | 0X1.p-1021 | 0X1.p-1021 | 1/22471164185778948846616314884862809170224712236778832159178760144716584475687620391588559665300942002640014234983924169707348721101802077811605928829934265547220986678108185659537777450155761764931635369010625721104768835292807860184239138817603404645418813835573287279993405742309964538104419541203028017152 | +| tst.go:10:7:10:15 | 1_000_000 | 1000000 | 1000000 | +| tst.go:11:7:11:18 | 0b_0101_0110 | 86 | 86 | +| tst.go:12:7:12:17 | 3.1415_9265 | 3.1415_9265 | 62831853/20000000 | diff --git a/ql/test/extractor-tests/go1.13/literals.ql b/ql/test/extractor-tests/go1.13/literals.ql new file mode 100644 index 00000000..a03622fa --- /dev/null +++ b/ql/test/extractor-tests/go1.13/literals.ql @@ -0,0 +1,4 @@ +import go + +from BasicLit l +select l, l.getValue(), l.getExactValue() diff --git a/ql/test/extractor-tests/go1.13/tst.go b/ql/test/extractor-tests/go1.13/tst.go new file mode 100644 index 00000000..53d4357f --- /dev/null +++ b/ql/test/extractor-tests/go1.13/tst.go @@ -0,0 +1,13 @@ +package main + +const ( + b1 = 0b1011 + b2 = 0B1011 + o1 = 0o660 + o2 = 0O660 + h1 = 0x1.p-1021 + h2 = 0X1.p-1021 + s1 = 1_000_000 + s2 = 0b_0101_0110 + s3 = 3.1415_9265 +) \ No newline at end of file diff --git a/ql/test/extractor-tests/robustness/tst.expected b/ql/test/extractor-tests/robustness/tst.expected new file mode 100644 index 00000000..cb2f8ad3 --- /dev/null +++ b/ql/test/extractor-tests/robustness/tst.expected @@ -0,0 +1 @@ +| success | diff --git a/ql/test/extractor-tests/robustness/tst.go b/ql/test/extractor-tests/robustness/tst.go new file mode 100644 index 00000000..f78985fa --- /dev/null +++ b/ql/test/extractor-tests/robustness/tst.go @@ -0,0 +1,7 @@ +package main + +// This file tests that the extractor does not crash on files that do not +// end in a newline, so it is important not to autoformat this file. + +func main() { +} \ No newline at end of file diff --git a/ql/test/extractor-tests/robustness/tst.ql b/ql/test/extractor-tests/robustness/tst.ql new file mode 100644 index 00000000..5eebd438 --- /dev/null +++ b/ql/test/extractor-tests/robustness/tst.ql @@ -0,0 +1,3 @@ +// trivial test; we only want to test that the extractor does not crash +// on the input files +select "success" diff --git a/ql/test/library-tests/semmle/go/Expr/BasicLit_getText.expected b/ql/test/library-tests/semmle/go/Expr/BasicLit_getText.expected new file mode 100644 index 00000000..b4edc9fb --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/BasicLit_getText.expected @@ -0,0 +1,74 @@ +| consts.go:4:2:4:6 | "fmt" | "fmt" | +| consts.go:5:2:5:9 | "unsafe" | "unsafe" | +| consts.go:8:9:8:9 | 0 | 0 | +| consts.go:11:6:11:8 | "a" | "a" | +| consts.go:13:6:13:6 | 4 | 4 | +| consts.go:17:10:17:10 | 1 | 1 | +| consts.go:21:9:21:9 | 1 | 1 | +| consts.go:25:7:25:7 | 1 | 1 | +| consts.go:31:14:31:20 | "value" | "value" | +| consts.go:31:23:31:29 | "split" | "split" | +| consts.go:31:31:31:38 | "string" | "string" | +| consts.go:32:3:32:4 | 20 | 20 | +| consts.go:32:7:32:7 | 3 | 3 | +| consts.go:32:9:32:9 | 2 | 2 | +| consts.go:32:12:32:13 | 2i | 2i | +| consts.go:32:16:32:20 | 0.24i | 0.24i | +| consts.go:32:23:32:23 | 1 | 1 | +| consts.go:32:25:32:26 | 1i | 1i | +| consts.go:32:29:32:31 | 2.3 | 2.3 | +| consts.go:32:33:32:36 | 9.7i | 9.7i | +| consts.go:33:3:33:5 | 'a' | 'a' | +| consts.go:33:8:33:13 | '\\x8b' | '\\x8b' | +| consts.go:33:16:33:38 | 3.141592653589793238462 | 3.141592653589793238462 | +| consts.go:33:40:33:40 | 8 | 8 | +| consts.go:45:16:45:17 | 3i | 3i | +| consts.go:46:16:46:16 | 2 | 2 | +| consts.go:47:16:47:18 | 1.0 | 1.0 | +| consts.go:48:16:48:19 | "hi" | "hi" | +| literals.go:3:8:3:12 | "fmt" | "fmt" | +| literals.go:6:2:6:10 | "decimal" | "decimal" | +| literals.go:6:17:6:18 | 42 | 42 | +| literals.go:7:2:7:8 | "octal" | "octal" | +| literals.go:7:17:7:20 | 0600 | 0600 | +| literals.go:8:2:8:14 | "hexadecimal" | "hexadecimal" | +| literals.go:8:17:8:24 | 0xcaffee | 0xcaffee | +| literals.go:12:2:12:3 | 0. | 0. | +| literals.go:13:2:13:6 | 72.40 | 72.40 | +| literals.go:14:2:14:7 | 072.40 | 072.40 | +| literals.go:15:2:15:8 | 2.71828 | 2.71828 | +| literals.go:16:2:16:6 | 1.e+0 | 1.e+0 | +| literals.go:17:2:17:12 | 6.67428e-11 | 6.67428e-11 | +| literals.go:18:2:18:4 | 1E6 | 1E6 | +| literals.go:19:2:19:4 | .25 | .25 | +| literals.go:20:2:20:10 | .12345E+5 | .12345E+5 | +| literals.go:24:2:24:3 | 0i | 0i | +| literals.go:25:2:25:5 | 011i | 011i | +| literals.go:26:2:26:4 | 0.i | 0.i | +| literals.go:27:2:27:9 | 2.71828i | 2.71828i | +| literals.go:28:2:28:7 | 1.e+0i | 1.e+0i | +| literals.go:29:2:29:13 | 6.67428e-11i | 6.67428e-11i | +| literals.go:30:2:30:5 | 1E6i | 1E6i | +| literals.go:31:2:31:5 | .25i | .25i | +| literals.go:32:2:32:11 | .12345E+5i | .12345E+5i | +| literals.go:36:2:36:4 | 'a' | 'a' | +| literals.go:37:2:37:5 | '\u00e4' | '\u00e4' | +| literals.go:38:2:38:6 | '\u672c' | '\u672c' | +| literals.go:39:2:39:5 | '\\t' | '\\t' | +| literals.go:40:2:40:7 | '\\007' | '\\007' | +| literals.go:41:2:41:7 | '\\377' | '\\377' | +| literals.go:42:2:42:7 | '\\x07' | '\\x07' | +| literals.go:43:2:43:7 | '\\xff' | '\\xff' | +| literals.go:44:2:44:9 | '\\u12e4' | '\\u12e4' | +| literals.go:45:2:45:13 | '\\U00101234' | '\\U00101234' | +| literals.go:46:2:46:5 | '\\'' | '\\'' | +| literals.go:50:2:50:6 | `abc` | `abc` | +| literals.go:51:2:52:3 | `\\n,\n\\n` | `\\n,\n\\n` | +| literals.go:53:2:53:5 | "\\n" | "\\n" | +| literals.go:54:2:54:5 | "\\"" | "\\"" | +| literals.go:55:2:55:18 | "Hello, world!\\n" | "Hello, world!\\n" | +| literals.go:56:2:56:12 | "\u65e5\u672c\u8a9e" | "\u65e5\u672c\u8a9e" | +| literals.go:57:2:57:22 | "\\u65e5\u672c\\U00008a9e" | "\\u65e5\u672c\\U00008a9e" | +| literals.go:58:2:58:13 | "\\xff\\u00FF" | "\\xff\\u00FF" | +| literals.go:67:22:67:27 | "\ud83d\udcff" | "\ud83d\udcff" | +| literals.go:67:31:67:32 | "" | "" | diff --git a/ql/test/library-tests/semmle/go/Expr/BasicLit_getText.ql b/ql/test/library-tests/semmle/go/Expr/BasicLit_getText.ql new file mode 100644 index 00000000..a3035806 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/BasicLit_getText.ql @@ -0,0 +1,4 @@ +import go + +from BasicLit bl +select bl, bl.getText() diff --git a/ql/test/library-tests/semmle/go/Expr/BasicLit_getValue.expected b/ql/test/library-tests/semmle/go/Expr/BasicLit_getValue.expected new file mode 100644 index 00000000..2dfd765d --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/BasicLit_getValue.expected @@ -0,0 +1,74 @@ +| consts.go:4:2:4:6 | "fmt" | fmt | +| consts.go:5:2:5:9 | "unsafe" | unsafe | +| consts.go:8:9:8:9 | 0 | 0 | +| consts.go:11:6:11:8 | "a" | a | +| consts.go:13:6:13:6 | 4 | 4 | +| consts.go:17:10:17:10 | 1 | 1 | +| consts.go:21:9:21:9 | 1 | 1 | +| consts.go:25:7:25:7 | 1 | 1 | +| consts.go:31:14:31:20 | "value" | value | +| consts.go:31:23:31:29 | "split" | split | +| consts.go:31:31:31:38 | "string" | string | +| consts.go:32:3:32:4 | 20 | 20 | +| consts.go:32:7:32:7 | 3 | 3 | +| consts.go:32:9:32:9 | 2 | 2 | +| consts.go:32:12:32:13 | 2i | 2i | +| consts.go:32:16:32:20 | 0.24i | 0.24i | +| consts.go:32:23:32:23 | 1 | 1 | +| consts.go:32:25:32:26 | 1i | 1i | +| consts.go:32:29:32:31 | 2.3 | 2.3 | +| consts.go:32:33:32:36 | 9.7i | 9.7i | +| consts.go:33:3:33:5 | 'a' | a | +| consts.go:33:8:33:13 | '\\x8b' | \ufffd | +| consts.go:33:16:33:38 | 3.141592653589793238462 | 3.141592653589793238462 | +| consts.go:33:40:33:40 | 8 | 8 | +| consts.go:45:16:45:17 | 3i | 3i | +| consts.go:46:16:46:16 | 2 | 2 | +| consts.go:47:16:47:18 | 1.0 | 1.0 | +| consts.go:48:16:48:19 | "hi" | hi | +| literals.go:3:8:3:12 | "fmt" | fmt | +| literals.go:6:2:6:10 | "decimal" | decimal | +| literals.go:6:17:6:18 | 42 | 42 | +| literals.go:7:2:7:8 | "octal" | octal | +| literals.go:7:17:7:20 | 0600 | 384 | +| literals.go:8:2:8:14 | "hexadecimal" | hexadecimal | +| literals.go:8:17:8:24 | 0xcaffee | 13303790 | +| literals.go:12:2:12:3 | 0. | 0. | +| literals.go:13:2:13:6 | 72.40 | 72.40 | +| literals.go:14:2:14:7 | 072.40 | 072.40 | +| literals.go:15:2:15:8 | 2.71828 | 2.71828 | +| literals.go:16:2:16:6 | 1.e+0 | 1.e+0 | +| literals.go:17:2:17:12 | 6.67428e-11 | 6.67428e-11 | +| literals.go:18:2:18:4 | 1E6 | 1E6 | +| literals.go:19:2:19:4 | .25 | .25 | +| literals.go:20:2:20:10 | .12345E+5 | .12345E+5 | +| literals.go:24:2:24:3 | 0i | 0i | +| literals.go:25:2:25:5 | 011i | 011i | +| literals.go:26:2:26:4 | 0.i | 0.i | +| literals.go:27:2:27:9 | 2.71828i | 2.71828i | +| literals.go:28:2:28:7 | 1.e+0i | 1.e+0i | +| literals.go:29:2:29:13 | 6.67428e-11i | 6.67428e-11i | +| literals.go:30:2:30:5 | 1E6i | 1E6i | +| literals.go:31:2:31:5 | .25i | .25i | +| literals.go:32:2:32:11 | .12345E+5i | .12345E+5i | +| literals.go:36:2:36:4 | 'a' | a | +| literals.go:37:2:37:5 | '\u00e4' | \u00e4 | +| literals.go:38:2:38:6 | '\u672c' | \u672c | +| literals.go:39:2:39:5 | '\\t' | \t | +| literals.go:40:2:40:7 | '\\007' | \u0007 | +| literals.go:41:2:41:7 | '\\377' | \ufffd | +| literals.go:42:2:42:7 | '\\x07' | \u0007 | +| literals.go:43:2:43:7 | '\\xff' | \ufffd | +| literals.go:44:2:44:9 | '\\u12e4' | \u12e4 | +| literals.go:45:2:45:13 | '\\U00101234' | \udbc4\ude34 | +| literals.go:46:2:46:5 | '\\'' | ' | +| literals.go:50:2:50:6 | `abc` | abc | +| literals.go:51:2:52:3 | `\\n,\n\\n` | \\n,\n\\n | +| literals.go:53:2:53:5 | "\\n" | \n | +| literals.go:54:2:54:5 | "\\"" | " | +| literals.go:55:2:55:18 | "Hello, world!\\n" | Hello, world!\n | +| literals.go:56:2:56:12 | "\u65e5\u672c\u8a9e" | \u65e5\u672c\u8a9e | +| literals.go:57:2:57:22 | "\\u65e5\u672c\\U00008a9e" | \u65e5\u672c\u8a9e | +| literals.go:58:2:58:13 | "\\xff\\u00FF" | \ufffd\u00ff | +| literals.go:67:22:67:27 | "\ud83d\udcff" | \ud83d\udcff | +| literals.go:67:31:67:32 | "" | | diff --git a/ql/test/library-tests/semmle/go/Expr/BasicLit_getValue.ql b/ql/test/library-tests/semmle/go/Expr/BasicLit_getValue.ql new file mode 100644 index 00000000..2dc5e32a --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/BasicLit_getValue.ql @@ -0,0 +1,4 @@ +import go + +from BasicLit bl +select bl, bl.getValue() diff --git a/ql/test/library-tests/semmle/go/Expr/CompositeLit.expected b/ql/test/library-tests/semmle/go/Expr/CompositeLit.expected new file mode 100644 index 00000000..704af546 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/CompositeLit.expected @@ -0,0 +1,43 @@ +| literals.go:5:15:9:1 | composite literal | 0 | key | literals.go:6:2:6:10 | "decimal" | +| literals.go:5:15:9:1 | composite literal | 0 | value | literals.go:6:17:6:18 | 42 | +| literals.go:5:15:9:1 | composite literal | 1 | key | literals.go:7:2:7:8 | "octal" | +| literals.go:5:15:9:1 | composite literal | 1 | value | literals.go:7:17:7:20 | 0600 | +| literals.go:5:15:9:1 | composite literal | 2 | key | literals.go:8:2:8:14 | "hexadecimal" | +| literals.go:5:15:9:1 | composite literal | 2 | value | literals.go:8:17:8:24 | 0xcaffee | +| literals.go:11:17:21:1 | composite literal | 0 | value | literals.go:12:2:12:3 | 0. | +| literals.go:11:17:21:1 | composite literal | 1 | value | literals.go:13:2:13:6 | 72.40 | +| literals.go:11:17:21:1 | composite literal | 2 | value | literals.go:14:2:14:7 | 072.40 | +| literals.go:11:17:21:1 | composite literal | 3 | value | literals.go:15:2:15:8 | 2.71828 | +| literals.go:11:17:21:1 | composite literal | 4 | value | literals.go:16:2:16:6 | 1.e+0 | +| literals.go:11:17:21:1 | composite literal | 5 | value | literals.go:17:2:17:12 | 6.67428e-11 | +| literals.go:11:17:21:1 | composite literal | 6 | value | literals.go:18:2:18:4 | 1E6 | +| literals.go:11:17:21:1 | composite literal | 7 | value | literals.go:19:2:19:4 | .25 | +| literals.go:11:17:21:1 | composite literal | 8 | value | literals.go:20:2:20:10 | .12345E+5 | +| literals.go:23:16:33:1 | composite literal | 0 | value | literals.go:24:2:24:3 | 0i | +| literals.go:23:16:33:1 | composite literal | 1 | value | literals.go:25:2:25:5 | 011i | +| literals.go:23:16:33:1 | composite literal | 2 | value | literals.go:26:2:26:4 | 0.i | +| literals.go:23:16:33:1 | composite literal | 3 | value | literals.go:27:2:27:9 | 2.71828i | +| literals.go:23:16:33:1 | composite literal | 4 | value | literals.go:28:2:28:7 | 1.e+0i | +| literals.go:23:16:33:1 | composite literal | 5 | value | literals.go:29:2:29:13 | 6.67428e-11i | +| literals.go:23:16:33:1 | composite literal | 6 | value | literals.go:30:2:30:5 | 1E6i | +| literals.go:23:16:33:1 | composite literal | 7 | value | literals.go:31:2:31:5 | .25i | +| literals.go:23:16:33:1 | composite literal | 8 | value | literals.go:32:2:32:11 | .12345E+5i | +| literals.go:35:16:47:1 | composite literal | 0 | value | literals.go:36:2:36:4 | 'a' | +| literals.go:35:16:47:1 | composite literal | 1 | value | literals.go:37:2:37:5 | '\u00e4' | +| literals.go:35:16:47:1 | composite literal | 2 | value | literals.go:38:2:38:6 | '\u672c' | +| literals.go:35:16:47:1 | composite literal | 3 | value | literals.go:39:2:39:5 | '\\t' | +| literals.go:35:16:47:1 | composite literal | 4 | value | literals.go:40:2:40:7 | '\\007' | +| literals.go:35:16:47:1 | composite literal | 5 | value | literals.go:41:2:41:7 | '\\377' | +| literals.go:35:16:47:1 | composite literal | 6 | value | literals.go:42:2:42:7 | '\\x07' | +| literals.go:35:16:47:1 | composite literal | 7 | value | literals.go:43:2:43:7 | '\\xff' | +| literals.go:35:16:47:1 | composite literal | 8 | value | literals.go:44:2:44:9 | '\\u12e4' | +| literals.go:35:16:47:1 | composite literal | 9 | value | literals.go:45:2:45:13 | '\\U00101234' | +| literals.go:35:16:47:1 | composite literal | 10 | value | literals.go:46:2:46:5 | '\\'' | +| literals.go:49:15:59:1 | composite literal | 0 | value | literals.go:50:2:50:6 | `abc` | +| literals.go:49:15:59:1 | composite literal | 1 | value | literals.go:51:2:52:3 | `\\n,\n\\n` | +| literals.go:49:15:59:1 | composite literal | 2 | value | literals.go:53:2:53:5 | "\\n" | +| literals.go:49:15:59:1 | composite literal | 3 | value | literals.go:54:2:54:5 | "\\"" | +| literals.go:49:15:59:1 | composite literal | 4 | value | literals.go:55:2:55:18 | "Hello, world!\\n" | +| literals.go:49:15:59:1 | composite literal | 5 | value | literals.go:56:2:56:12 | "\u65e5\u672c\u8a9e" | +| literals.go:49:15:59:1 | composite literal | 6 | value | literals.go:57:2:57:22 | "\\u65e5\u672c\\U00008a9e" | +| literals.go:49:15:59:1 | composite literal | 7 | value | literals.go:58:2:58:13 | "\\xff\\u00FF" | diff --git a/ql/test/library-tests/semmle/go/Expr/CompositeLit.ql b/ql/test/library-tests/semmle/go/Expr/CompositeLit.ql new file mode 100644 index 00000000..b97b7288 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/CompositeLit.ql @@ -0,0 +1,8 @@ +import go + +from CompositeLit c, int i, string s, Expr e +where + s = "key" and e = c.getKey(i) + or + s = "value" and e = c.getValue(i) +select c, i, s, e diff --git a/ql/test/library-tests/semmle/go/Expr/ConstantValues.expected b/ql/test/library-tests/semmle/go/Expr/ConstantValues.expected new file mode 100644 index 00000000..69602669 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/ConstantValues.expected @@ -0,0 +1,29 @@ +| consts.go:30:14:30:14 | a | a | a | +| consts.go:30:17:30:17 | b | b | a | +| consts.go:30:20:30:20 | c | c | 4, 4 | +| consts.go:30:23:30:23 | d | d | 3, 3 | +| consts.go:30:26:30:26 | e | e | 4, 4 | +| consts.go:31:14:31:20 | "value" | "value" | value | +| consts.go:31:23:31:29 | "split" | "split" | split | +| consts.go:31:23:31:38 | ...+... | "split" + "string" | splitstring | +| consts.go:31:31:31:38 | "string" | "string" | string | +| consts.go:32:3:32:4 | 20 | 20 | 20, 20 | +| consts.go:32:7:32:7 | 3 | 3 | 3, 3 | +| consts.go:32:7:32:9 | ...+... | 3 + 2 | 5, 5 | +| consts.go:32:9:32:9 | 2 | 2 | 2, 2 | +| consts.go:32:12:32:13 | 2i | 2i | (0 + 2i), (0 + 2i) | +| consts.go:32:16:32:20 | 0.24i | 0.24i | (0 + 0.24i), (0 + 1080863910568919/4503599627370496i) | +| consts.go:32:23:32:26 | ...+... | 1 + 1i | (1 + 1i), (1 + 1i) | +| consts.go:32:25:32:26 | 1i | 1i | (0 + 1i), (0 + 1i) | +| consts.go:32:29:32:36 | ...-... | 2.3 - 9.7i | (2.3 + -9.7i), (2589569785738035/1125899906842624 + -2730307274093363/281474976710656i) | +| consts.go:32:33:32:36 | 9.7i | 9.7i | (0 + 9.7i), (0 + 97/10i) | +| consts.go:33:3:33:5 | 'a' | 'a' | 97, 97 | +| consts.go:33:8:33:13 | '\\x8b' | '\\x8b' | 139, 139 | +| consts.go:33:16:33:38 | 3.141592653589793238462 | 3.141592653589793238462 | 3.141593, 1570796326794896619231/500000000000000000000 | +| consts.go:33:16:33:40 | ...*... | 3.141592653589793238462 * 8 | 25.132741, 884279719003555/35184372088832 | +| consts.go:33:40:33:40 | 8 | 8 | 8, 8 | +| consts.go:34:14:34:14 | h | h | true | +| consts.go:34:17:34:17 | j | j | (0 + 3i), (0 + 3i) | +| consts.go:34:20:34:20 | k | k | 2, 2 | +| consts.go:34:23:34:23 | l | l | 1, 1 | +| consts.go:34:26:34:26 | m | m | hi | diff --git a/ql/test/library-tests/semmle/go/Expr/ConstantValues.ql b/ql/test/library-tests/semmle/go/Expr/ConstantValues.ql new file mode 100644 index 00000000..067b02d9 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/ConstantValues.ql @@ -0,0 +1,31 @@ +import go + +/** Gets the string representation of the complex number `real` + `imag`*i */ +bindingset[real, imag] +string complexToString(float real, float imag) { + result = "(" + real.toString() + " + " + imag.toString() + "i)" +} + +string longString(Expr e) { + if e instanceof BinaryExpr + then + result = longString(e.(BinaryExpr).getLeftOperand()) + " " + e.(BinaryExpr).getOperator() + " " + + longString(e.(BinaryExpr).getRightOperand()) + else result = e.toString() +} + +from CallExpr c, Expr e, string val +where + (e = c.getAnArgument() or e = c.getAnArgument().getAChildExpr()) and + ( + val = e.getBoolValue().toString() + or + val = e.getStringValue() + or + val = e.getNumericValue().toString() + ", " + e.getExactValue() + or + exists(float r, float i | e.hasComplexValue(r, i) | + val = complexToString(r, i) + ", " + e.getExactValue() + ) + ) +select e, longString(e), val diff --git a/ql/test/library-tests/semmle/go/Expr/Ident.expected b/ql/test/library-tests/semmle/go/Expr/Ident.expected new file mode 100644 index 00000000..d0b8634a --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/Ident.expected @@ -0,0 +1,80 @@ +| consts.go:1:9:1:12 | main | +| consts.go:8:5:8:5 | i | +| consts.go:11:2:11:2 | a | +| consts.go:12:2:12:2 | b | +| consts.go:13:2:13:2 | c | +| consts.go:14:2:14:2 | d | +| consts.go:14:6:14:9 | iota | +| consts.go:15:2:15:2 | e | +| consts.go:16:2:16:2 | f | +| consts.go:16:6:16:11 | unsafe | +| consts.go:16:13:16:18 | Sizeof | +| consts.go:16:20:16:22 | one | +| consts.go:17:2:17:2 | g | +| consts.go:17:6:17:6 | f | +| consts.go:20:6:20:8 | one | +| consts.go:20:12:20:14 | int | +| consts.go:24:6:24:8 | inc | +| consts.go:24:12:24:14 | int | +| consts.go:25:2:25:2 | i | +| consts.go:26:9:26:9 | i | +| consts.go:29:6:29:9 | main | +| consts.go:30:2:30:4 | fmt | +| consts.go:30:6:30:12 | Println | +| consts.go:30:14:30:14 | a | +| consts.go:30:17:30:17 | b | +| consts.go:30:20:30:20 | c | +| consts.go:30:23:30:23 | d | +| consts.go:30:26:30:26 | e | +| consts.go:30:29:30:29 | f | +| consts.go:30:32:30:32 | g | +| consts.go:31:2:31:4 | fmt | +| consts.go:31:6:31:12 | Println | +| consts.go:34:2:34:4 | fmt | +| consts.go:34:6:34:12 | Println | +| consts.go:34:14:34:14 | h | +| consts.go:34:17:34:17 | j | +| consts.go:34:20:34:20 | k | +| consts.go:34:23:34:23 | l | +| consts.go:34:26:34:26 | m | +| consts.go:37:6:37:11 | mybool | +| consts.go:37:13:37:16 | bool | +| consts.go:38:6:38:14 | mycomplex | +| consts.go:38:16:38:25 | complex128 | +| consts.go:39:6:39:10 | myint | +| consts.go:39:12:39:14 | int | +| consts.go:40:6:40:12 | myfloat | +| consts.go:40:14:40:20 | float64 | +| consts.go:41:6:41:13 | mystring | +| consts.go:41:15:41:20 | string | +| consts.go:44:2:44:2 | h | +| consts.go:44:4:44:9 | mybool | +| consts.go:44:16:44:19 | true | +| consts.go:45:2:45:2 | j | +| consts.go:45:4:45:12 | mycomplex | +| consts.go:46:2:46:2 | k | +| consts.go:46:4:46:8 | myint | +| consts.go:47:2:47:2 | l | +| consts.go:47:4:47:10 | myfloat | +| consts.go:48:2:48:2 | m | +| consts.go:48:4:48:11 | mystring | +| literals.go:1:9:1:12 | main | +| literals.go:5:5:5:11 | intlits | +| literals.go:5:19:5:24 | string | +| literals.go:5:26:5:28 | int | +| literals.go:11:5:11:13 | floatlits | +| literals.go:11:19:11:25 | float64 | +| literals.go:23:5:23:12 | imaglits | +| literals.go:23:18:23:26 | complex64 | +| literals.go:35:5:35:12 | runelits | +| literals.go:35:18:35:20 | int | +| literals.go:49:5:49:11 | strlits | +| literals.go:49:17:49:22 | string | +| literals.go:61:6:61:13 | literals | +| literals.go:62:6:62:6 | _ | +| literals.go:62:9:62:11 | str | +| literals.go:62:22:62:28 | strlits | +| literals.go:63:3:63:5 | fmt | +| literals.go:63:7:63:13 | Println | +| literals.go:63:15:63:17 | str | +| literals.go:67:5:67:18 | non_bmp_strlit | diff --git a/ql/test/library-tests/semmle/go/Expr/Ident.ql b/ql/test/library-tests/semmle/go/Expr/Ident.ql new file mode 100644 index 00000000..9f386b09 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/Ident.ql @@ -0,0 +1,4 @@ +import go + +from Ident id +select id diff --git a/ql/test/library-tests/semmle/go/Expr/consts.go b/ql/test/library-tests/semmle/go/Expr/consts.go new file mode 100644 index 00000000..defb8bf8 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/consts.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "unsafe" +) + +var i = 0 + +const ( + a = "a" + b + c = 4 + d = iota + e + f = unsafe.Sizeof(one()) + g = f + 1 +) + +func one() int { + return 1 +} + +func inc() int { + i += 1 + return i +} + +func main() { + fmt.Println(a, b, c, d, e, f, g) + fmt.Println("value", "split"+"string", + 20, 3+2, 2i, 0.24i, 1+1i, 2.3-9.7i, + 'a', '\x8b', 3.141592653589793238462*8) + fmt.Println(h, j, k, l, m) +} + +type mybool bool +type mycomplex complex128 +type myint int +type myfloat float64 +type mystring string + +const ( + h mybool = true + j mycomplex = 3i + k myint = 2 + l myfloat = 1.0 + m mystring = "hi" +) diff --git a/ql/test/library-tests/semmle/go/Expr/literals.go b/ql/test/library-tests/semmle/go/Expr/literals.go new file mode 100644 index 00000000..fe9e002f --- /dev/null +++ b/ql/test/library-tests/semmle/go/Expr/literals.go @@ -0,0 +1,67 @@ +package main + +import "fmt" + +var intlits = map[string]int{ + "decimal": 42, + "octal": 0600, + "hexadecimal": 0xcaffee, +} + +var floatlits = []float64{ + 0., + 72.40, + 072.40, + 2.71828, + 1.e+0, + 6.67428e-11, + 1E6, + .25, + .12345E+5, +} + +var imaglits = []complex64{ + 0i, + 011i, + 0.i, + 2.71828i, + 1.e+0i, + 6.67428e-11i, + 1E6i, + .25i, + .12345E+5i, +} + +var runelits = []int{ + 'a', + 'ä', + '本', + '\t', + '\007', + '\377', + '\x07', + '\xff', + '\u12e4', + '\U00101234', + '\'', +} + +var strlits = []string{ + `abc`, + `\n, +\n`, + "\n", + "\"", + "Hello, world!\n", + "日本語", + "\u65e5本\U00008a9e", + "\xff\u00FF", +} + +func literals() { + for _, str := range strlits { + fmt.Println(str) + } +} + +var non_bmp_strlit = "📿" + "" diff --git a/ql/test/library-tests/semmle/go/Scopes/DeclaredEntity.expected b/ql/test/library-tests/semmle/go/Scopes/DeclaredEntity.expected new file mode 100644 index 00000000..a16a6755 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/DeclaredEntity.expected @@ -0,0 +1,20 @@ +| a | types.go:24:22:24:22 | a | +| foo | main.go:17:6:17:8 | foo | +| iHaveAMethod | types.go:3:6:3:17 | iHaveAMethod | +| main | main.go:9:6:9:9 | main | +| meth | main.go:13:16:13:19 | meth | +| meth | types.go:4:2:4:5 | meth | +| meth1 | types.go:8:2:8:6 | meth1 | +| meth1 | types.go:14:18:14:22 | meth1 | +| meth1 | types.go:24:16:24:20 | meth1 | +| meth2 | types.go:9:2:9:6 | meth2 | +| meth2 | types.go:18:17:18:21 | meth2 | +| meth2 | types.go:28:16:28:20 | meth2 | +| notImpl | types.go:22:6:22:12 | notImpl | +| recv | main.go:13:7:13:10 | recv | +| starImpl | types.go:12:6:12:13 | starImpl | +| t | main.go:5:6:5:6 | t | +| twoMethods | types.go:7:6:7:15 | twoMethods | +| x | main.go:6:2:6:2 | x | +| x | main.go:17:10:17:10 | x | +| y | main.go:17:26:17:26 | y | diff --git a/ql/test/library-tests/semmle/go/Scopes/DeclaredEntity.ql b/ql/test/library-tests/semmle/go/Scopes/DeclaredEntity.ql new file mode 100644 index 00000000..5686a96d --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/DeclaredEntity.ql @@ -0,0 +1,4 @@ +import go + +from DeclaredEntity e +select e, e.getDeclaration() diff --git a/ql/test/library-tests/semmle/go/Scopes/EntityType.expected b/ql/test/library-tests/semmle/go/Scopes/EntityType.expected new file mode 100644 index 00000000..96b5059f --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/EntityType.expected @@ -0,0 +1,20 @@ +| a | int | +| foo | func(iHaveAMethod, * t) | +| iHaveAMethod | iHaveAMethod | +| main | func() | +| meth | func() int | +| meth | func() int | +| meth1 | func() bool | +| meth1 | func() bool | +| meth1 | func(int) bool | +| meth2 | func() int | +| meth2 | func() int | +| meth2 | func() int | +| notImpl | notImpl | +| recv | * t | +| starImpl | starImpl | +| t | t | +| twoMethods | twoMethods | +| x | iHaveAMethod | +| x | int | +| y | * t | diff --git a/ql/test/library-tests/semmle/go/Scopes/EntityType.ql b/ql/test/library-tests/semmle/go/Scopes/EntityType.ql new file mode 100644 index 00000000..94967239 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/EntityType.ql @@ -0,0 +1,5 @@ +import go + +from Entity e +where exists(e.getDeclaration()) +select e, e.getType().pp() diff --git a/ql/test/library-tests/semmle/go/Scopes/EntityUse.expected b/ql/test/library-tests/semmle/go/Scopes/EntityUse.expected new file mode 100644 index 00000000..2ca658ff --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/EntityUse.expected @@ -0,0 +1,30 @@ +| Println | | main.go:10:6:10:12 | Println | +| a | types.go@24:22-24:22 | types.go:25:9:25:9 | a | +| bool | | types.go:8:10:8:13 | bool | +| bool | | types.go:14:26:14:29 | bool | +| bool | | types.go:24:29:24:32 | bool | +| false | | types.go:15:9:15:13 | false | +| fmt | | main.go:10:2:10:4 | fmt | +| iHaveAMethod | types.go@3:6-3:17 | main.go:17:12:17:23 | iHaveAMethod | +| int | | main.go:6:4:6:6 | int | +| int | | main.go:13:23:13:25 | int | +| int | | types.go:4:9:4:11 | int | +| int | | types.go:9:10:9:12 | int | +| int | | types.go:18:25:18:27 | int | +| int | | types.go:24:24:24:26 | int | +| int | | types.go:28:24:28:26 | int | +| meth | main.go@13:16-13:19 | main.go:19:4:19:7 | meth | +| meth | main.go@13:16-13:19 | main.go:20:7:20:10 | meth | +| meth | types.go@4:2-4:5 | main.go:18:4:18:7 | meth | +| notImpl | types.go@22:6-22:12 | types.go:24:7:24:13 | notImpl | +| notImpl | types.go@22:6-22:12 | types.go:28:7:28:13 | notImpl | +| recv | main.go@13:7-13:10 | main.go:14:9:14:12 | recv | +| starImpl | types.go@12:6-12:13 | types.go:14:8:14:15 | starImpl | +| starImpl | types.go@12:6-12:13 | types.go:18:7:18:14 | starImpl | +| t | main.go@5:6-5:6 | main.go:13:13:13:13 | t | +| t | main.go@5:6-5:6 | main.go:17:29:17:29 | t | +| t | main.go@5:6-5:6 | main.go:20:4:20:4 | t | +| x | main.go@6:2-6:2 | main.go:14:14:14:14 | x | +| x | main.go@17:10-17:10 | main.go:18:2:18:2 | x | +| y | main.go@17:26-17:26 | main.go:19:2:19:2 | y | +| y | main.go@17:26-17:26 | main.go:20:12:20:12 | y | diff --git a/ql/test/library-tests/semmle/go/Scopes/EntityUse.ql b/ql/test/library-tests/semmle/go/Scopes/EntityUse.ql new file mode 100644 index 00000000..7e893263 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/EntityUse.ql @@ -0,0 +1,9 @@ +import go + +from Entity e, string declloc +where + declloc = "file://" + e.getDeclaration().getLocation().toString() + or + not exists(e.getDeclaration()) and + declloc = "" +select e, declloc, e.getAUse() diff --git a/ql/test/library-tests/semmle/go/Scopes/MethodImplements.expected b/ql/test/library-tests/semmle/go/Scopes/MethodImplements.expected new file mode 100644 index 00000000..47e71985 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/MethodImplements.expected @@ -0,0 +1,6 @@ +| iHaveAMethod | meth | iHaveAMethod | meth | +| pointer type | meth | iHaveAMethod | meth | +| pointer type | meth1 | twoMethods | meth1 | +| starImpl | meth2 | twoMethods | meth2 | +| twoMethods | meth1 | twoMethods | meth1 | +| twoMethods | meth2 | twoMethods | meth2 | diff --git a/ql/test/library-tests/semmle/go/Scopes/MethodImplements.ql b/ql/test/library-tests/semmle/go/Scopes/MethodImplements.ql new file mode 100644 index 00000000..7def1770 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/MethodImplements.ql @@ -0,0 +1,5 @@ +import go + +from Method m, Method im +where m.implements(im) and m.getPackage().getName() = "main" +select m.getReceiverType(), m.getName(), im.getReceiverType(), im.getName() diff --git a/ql/test/library-tests/semmle/go/Scopes/MethodImplementsName.expected b/ql/test/library-tests/semmle/go/Scopes/MethodImplementsName.expected new file mode 100644 index 00000000..e72234dd --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/MethodImplementsName.expected @@ -0,0 +1,6 @@ +| iHaveAMethod | meth | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes | iHaveAMethod | meth | +| pointer type | meth | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes | iHaveAMethod | meth | +| pointer type | meth1 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes | twoMethods | meth1 | +| starImpl | meth2 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes | twoMethods | meth2 | +| twoMethods | meth1 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes | twoMethods | meth1 | +| twoMethods | meth2 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes | twoMethods | meth2 | diff --git a/ql/test/library-tests/semmle/go/Scopes/MethodImplementsName.ql b/ql/test/library-tests/semmle/go/Scopes/MethodImplementsName.ql new file mode 100644 index 00000000..89e6fb47 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/MethodImplementsName.ql @@ -0,0 +1,7 @@ +import go + +from Method m, string pkg, string tp, string name +where + m.implements(pkg, tp, name) and + m.hasQualifiedName("github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes", _, _) +select m.getReceiverType(), m.getName(), pkg, tp, name diff --git a/ql/test/library-tests/semmle/go/Scopes/Methods.expected b/ql/test/library-tests/semmle/go/Scopes/Methods.expected new file mode 100644 index 00000000..ec4fb1aa --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/Methods.expected @@ -0,0 +1,8 @@ +| meth | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes.iHaveAMethod.meth | | iHaveAMethod | +| meth | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes.t.meth | recv | * t | +| meth1 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes.notImpl.meth1 | | notImpl | +| meth1 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes.starImpl.meth1 | | * starImpl | +| meth1 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes.twoMethods.meth1 | | twoMethods | +| meth2 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes.notImpl.meth2 | | notImpl | +| meth2 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes.starImpl.meth2 | | starImpl | +| meth2 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes.twoMethods.meth2 | | twoMethods | diff --git a/ql/test/library-tests/semmle/go/Scopes/Methods.ql b/ql/test/library-tests/semmle/go/Scopes/Methods.ql new file mode 100644 index 00000000..88a14073 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/Methods.ql @@ -0,0 +1,5 @@ +import go + +from Method m +where m.getPackage().getName() = "main" +select m, m.getQualifiedName(), m.getReceiver(), m.getReceiverType().pp() diff --git a/ql/test/library-tests/semmle/go/Scopes/TypeImplements.expected b/ql/test/library-tests/semmle/go/Scopes/TypeImplements.expected new file mode 100644 index 00000000..06a6e5ee --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/TypeImplements.expected @@ -0,0 +1,6 @@ +| * starImpl | twoMethods | +| * t | iHaveAMethod | +| iHaveAMethod | iHaveAMethod | +| interface { meth1 func() bool; meth2 func() int } | twoMethods | +| interface { meth func() int } | iHaveAMethod | +| twoMethods | twoMethods | diff --git a/ql/test/library-tests/semmle/go/Scopes/TypeImplements.ql b/ql/test/library-tests/semmle/go/Scopes/TypeImplements.ql new file mode 100644 index 00000000..f4827bef --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/TypeImplements.ql @@ -0,0 +1,5 @@ +import go + +from Type t, string iface +where t.implements("github.com/Semmle/go/ql/test/library-tests/semmle/go/Scopes", iface) +select t.pp(), iface diff --git a/ql/test/library-tests/semmle/go/Scopes/main.go b/ql/test/library-tests/semmle/go/Scopes/main.go new file mode 100644 index 00000000..e1a9bab9 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/main.go @@ -0,0 +1,21 @@ +package main + +import "fmt" + +type t struct { + x int +} + +func main() { + fmt.Println("hi") +} + +func (recv *t) meth() int { + return recv.x +} + +func foo(x iHaveAMethod, y *t) { + x.meth() + y.meth() + (*t).meth(y) +} diff --git a/ql/test/library-tests/semmle/go/Scopes/types.go b/ql/test/library-tests/semmle/go/Scopes/types.go new file mode 100644 index 00000000..eab2b183 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Scopes/types.go @@ -0,0 +1,30 @@ +package main + +type iHaveAMethod interface { + meth() int +} + +type twoMethods interface { + meth1() bool + meth2() int +} + +type starImpl struct{} + +func (*starImpl) meth1() bool { + return false +} + +func (starImpl) meth2() int { + return 42 +} + +type notImpl struct{} + +func (notImpl) meth1(a int) bool { + return a == 42 +} + +func (notImpl) meth2() int { + return -42 +} diff --git a/ql/test/library-tests/semmle/go/Types/MethodDecls.expected b/ql/test/library-tests/semmle/go/Types/MethodDecls.expected new file mode 100644 index 00000000..6dac3679 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/MethodDecls.expected @@ -0,0 +1 @@ +| Foo | half | pkg1/tst.go:33:1:35:1 | function declaration | diff --git a/ql/test/library-tests/semmle/go/Types/MethodDecls.ql b/ql/test/library-tests/semmle/go/Types/MethodDecls.ql new file mode 100644 index 00000000..c0fb55f3 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/MethodDecls.ql @@ -0,0 +1,4 @@ +import go + +from NamedType t, string m +select t, m, t.getMethodDecl(m) diff --git a/ql/test/library-tests/semmle/go/Types/Methods.expected b/ql/test/library-tests/semmle/go/Types/Methods.expected new file mode 100644 index 00000000..a812cc5f --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/Methods.expected @@ -0,0 +1,3 @@ +| T | half | func() Foo | +| T3 | half | func() Foo | +| T4 | half | func() Foo | diff --git a/ql/test/library-tests/semmle/go/Types/Methods.ql b/ql/test/library-tests/semmle/go/Types/Methods.ql new file mode 100644 index 00000000..74a59761 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/Methods.ql @@ -0,0 +1,7 @@ +import go + +from NamedType t, string m, Type tp +where + exists(t.getEntity().getDeclaration()) and + t.getBaseType().hasMethod(m, tp) +select t, m, tp.pp() diff --git a/ql/test/library-tests/semmle/go/Types/QualifiedNames.expected b/ql/test/library-tests/semmle/go/Types/QualifiedNames.expected new file mode 100644 index 00000000..079df401 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/QualifiedNames.expected @@ -0,0 +1,8 @@ +| Bar | github.com/Semmle/go/ql/test/library-tests/semmle/go/Types/pkg1.Bar | +| Foo | github.com/Semmle/go/ql/test/library-tests/semmle/go/Types/pkg1.Foo | +| G | github.com/Semmle/go/ql/test/library-tests/semmle/go/Types/pkg2.G | +| T | github.com/Semmle/go/ql/test/library-tests/semmle/go/Types/pkg1.T | +| T | github.com/Semmle/go/ql/test/library-tests/semmle/go/Types/pkg2.T | +| T2 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Types/pkg1.T2 | +| T3 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Types/pkg1.T3 | +| T4 | github.com/Semmle/go/ql/test/library-tests/semmle/go/Types/pkg1.T4 | diff --git a/ql/test/library-tests/semmle/go/Types/QualifiedNames.ql b/ql/test/library-tests/semmle/go/Types/QualifiedNames.ql new file mode 100644 index 00000000..c438a357 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/QualifiedNames.ql @@ -0,0 +1,5 @@ +import go + +from Type t +where exists(t.getEntity().getDeclaration()) +select t, t.getQualifiedName() diff --git a/ql/test/library-tests/semmle/go/Types/SignatureType_getNumParameter.expected b/ql/test/library-tests/semmle/go/Types/SignatureType_getNumParameter.expected new file mode 100644 index 00000000..1e7e4fd9 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/SignatureType_getNumParameter.expected @@ -0,0 +1,6 @@ +| main.go:5:1:5:30 | function declaration | 1 | +| main.go:7:1:9:1 | function declaration | 2 | +| main.go:11:1:11:14 | function declaration | 0 | +| pkg1/tst.go:33:1:35:1 | function declaration | 0 | +| pkg1/tst.go:37:1:37:26 | function declaration | 1 | +| pkg1/tst.go:39:1:57:1 | function declaration | 2 | diff --git a/ql/test/library-tests/semmle/go/Types/SignatureType_getNumParameter.ql b/ql/test/library-tests/semmle/go/Types/SignatureType_getNumParameter.ql new file mode 100644 index 00000000..fa9c6859 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/SignatureType_getNumParameter.ql @@ -0,0 +1,4 @@ +import go + +from FuncDef fd +select fd, fd.getType().getNumParameter() diff --git a/ql/test/library-tests/semmle/go/Types/SignatureType_getNumResult.expected b/ql/test/library-tests/semmle/go/Types/SignatureType_getNumResult.expected new file mode 100644 index 00000000..d38ddcce --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/SignatureType_getNumResult.expected @@ -0,0 +1,6 @@ +| main.go:5:1:5:30 | function declaration | 0 | +| main.go:7:1:9:1 | function declaration | 2 | +| main.go:11:1:11:14 | function declaration | 0 | +| pkg1/tst.go:33:1:35:1 | function declaration | 1 | +| pkg1/tst.go:37:1:37:26 | function declaration | 0 | +| pkg1/tst.go:39:1:57:1 | function declaration | 0 | diff --git a/ql/test/library-tests/semmle/go/Types/SignatureType_getNumResult.ql b/ql/test/library-tests/semmle/go/Types/SignatureType_getNumResult.ql new file mode 100644 index 00000000..280df5b8 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/SignatureType_getNumResult.ql @@ -0,0 +1,4 @@ +import go + +from FuncDef fd +select fd, fd.getType().getNumResult() diff --git a/ql/test/library-tests/semmle/go/Types/StructFields.expected b/ql/test/library-tests/semmle/go/Types/StructFields.expected new file mode 100644 index 00000000..2f8ed48b --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/StructFields.expected @@ -0,0 +1,21 @@ +| Bar | pkg1/tst.go:29:10:31:1 | struct type | flag | bool | +| Foo | pkg1/tst.go:24:10:27:1 | struct type | flag | bool | +| Foo | pkg1/tst.go:24:10:27:1 | struct type | val | int | +| G | pkg2/tst.go:3:8:5:1 | struct type | g | int | +| G | pkg2/tst.go:7:8:9:1 | struct type | g | int | +| T | pkg1/tst.go:3:8:7:1 | struct type | Bar | Bar | +| T | pkg1/tst.go:3:8:7:1 | struct type | Foo | Foo | +| T | pkg1/tst.go:3:8:7:1 | struct type | f | int | +| T | pkg1/tst.go:3:8:7:1 | struct type | val | int | +| T | pkg2/tst.go:3:8:5:1 | struct type | g | int | +| T | pkg2/tst.go:7:8:9:1 | struct type | g | int | +| T2 | pkg1/tst.go:9:9:12:1 | struct type | Bar | Bar | +| T2 | pkg1/tst.go:9:9:12:1 | struct type | Foo | Foo | +| T2 | pkg1/tst.go:9:9:12:1 | struct type | flag | bool | +| T3 | pkg1/tst.go:14:9:17:1 | struct type | Bar | * Bar | +| T3 | pkg1/tst.go:14:9:17:1 | struct type | Foo | * Foo | +| T3 | pkg1/tst.go:14:9:17:1 | struct type | val | int | +| T4 | pkg1/tst.go:19:9:22:1 | struct type | Bar | Bar | +| T4 | pkg1/tst.go:19:9:22:1 | struct type | Foo | * Foo | +| T4 | pkg1/tst.go:19:9:22:1 | struct type | flag | bool | +| T4 | pkg1/tst.go:19:9:22:1 | struct type | val | int | diff --git a/ql/test/library-tests/semmle/go/Types/StructFields.ql b/ql/test/library-tests/semmle/go/Types/StructFields.ql new file mode 100644 index 00000000..0e0b93b2 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/StructFields.ql @@ -0,0 +1,7 @@ +import go + +from StructTypeExpr ste, NamedType named, string name, Type tp +where + named.getUnderlyingType() = ste.getType() and + ste.getType().(StructType).hasField(name, tp) +select named, ste, name, tp.pp() diff --git a/ql/test/library-tests/semmle/go/Types/Types.expected b/ql/test/library-tests/semmle/go/Types/Types.expected new file mode 100644 index 00000000..13a4c8e2 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/Types.expected @@ -0,0 +1,8 @@ +| Bar | Bar | +| Foo | Foo | +| G | G | +| T | T | +| T | T | +| T2 | T2 | +| T3 | T3 | +| T4 | T4 | diff --git a/ql/test/library-tests/semmle/go/Types/Types.ql b/ql/test/library-tests/semmle/go/Types/Types.ql new file mode 100644 index 00000000..a412b675 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/Types.ql @@ -0,0 +1,5 @@ +import go + +from Type t +where exists(t.getEntity().getDeclaration()) +select t, t.pp() diff --git a/ql/test/library-tests/semmle/go/Types/main.go b/ql/test/library-tests/semmle/go/Types/main.go new file mode 100644 index 00000000..1c7e1684 --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/main.go @@ -0,0 +1,11 @@ +package main + +import "regexp" + +func test(r *regexp.Regexp) {} + +func swap(x int, y int) (int, int) { + return y, x +} + +func main() {} diff --git a/ql/test/library-tests/semmle/go/Types/pkg1/tst.go b/ql/test/library-tests/semmle/go/Types/pkg1/tst.go new file mode 100644 index 00000000..1b1920ff --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/pkg1/tst.go @@ -0,0 +1,57 @@ +package pkg1 + +type T struct { + f int + Foo + Bar +} + +type T2 struct { + Foo Foo + Bar +} + +type T3 struct { + *Foo + *Bar +} + +type T4 struct { + *Foo + Bar Bar +} + +type Foo struct { + val int + flag bool +} + +type Bar struct { + flag bool +} + +func (foo Foo) half() Foo { + return Foo{foo.val / 2, true} +} + +func use(x interface{}) {} + +func test(t T, t2 T2) { + // fields of T + use(t.Bar) + use(t.f) + // illegal: use(t.flag) + use(t.Foo) + use(t.val) + + // methods of T + t.half() + + // fields of T2 + use(t2.Bar) + use(t2.flag) + use(t2.Foo) + + // methods of T2 + // illegal: t2.half() +} diff --git a/ql/test/library-tests/semmle/go/Types/pkg2/tst.go b/ql/test/library-tests/semmle/go/Types/pkg2/tst.go new file mode 100644 index 00000000..7f6f7f2d --- /dev/null +++ b/ql/test/library-tests/semmle/go/Types/pkg2/tst.go @@ -0,0 +1,9 @@ +package pkg2 + +type T struct { + g int +} + +type G struct { + g int +} diff --git a/ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.expected b/ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.expected new file mode 100644 index 00000000..831631d3 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.expected @@ -0,0 +1,7 @@ +| HTMLEscape | html | +| HTMLEscapeString | html | +| HTMLEscaper | html | +| JSEscape | js | +| JSEscapeString | js | +| JSEscaper | js | +| URLQueryEscaper | url | diff --git a/ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.go b/ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.go new file mode 100644 index 00000000..bbff30e5 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.go @@ -0,0 +1,14 @@ +package main + +// this is a dummy file that imports the relevant libraries so the tests +// can pick up the functions that should be detected. + +import ( + "bytes" + "text/template" +) + +func main() { + buf := bytes.NewBufferString("") + template.HTMLEscape(buf, []byte("")) +} diff --git a/ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.ql b/ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.ql new file mode 100644 index 00000000..c2a8fabd --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/EscapeFunction/EscapeFunction.ql @@ -0,0 +1,4 @@ +import go + +from EscapeFunction efn +select efn, efn.kind() diff --git a/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpMatchFunction.expected b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpMatchFunction.expected new file mode 100644 index 00000000..487c1be0 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpMatchFunction.expected @@ -0,0 +1,3 @@ +| Match | stdlib.go:16:30:16:37 | "posix?" | stdlib.go:28:11:28:24 | type conversion | stdlib.go:28:2:28:25 | call to Match | +| MatchReader | stdlib.go:16:30:16:37 | "posix?" | stdlib.go:29:17:29:37 | call to NewReader | stdlib.go:29:2:29:38 | call to MatchReader | +| MatchString | stdlib.go:16:30:16:37 | "posix?" | stdlib.go:17:17:17:22 | "posi" | stdlib.go:17:2:17:23 | call to MatchString | diff --git a/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpMatchFunction.ql b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpMatchFunction.ql new file mode 100644 index 00000000..ccdbcde4 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpMatchFunction.ql @@ -0,0 +1,5 @@ +import go + +from RegexpMatchFunction match, DataFlow::CallNode call +where call = match.getACall() +select match, match.getRegexp(call), match.getValue().getNode(call), match.getResult().getNode(call) diff --git a/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpPattern.expected b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpPattern.expected new file mode 100644 index 00000000..1890c5c4 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpPattern.expected @@ -0,0 +1,22 @@ +| stdlib.go:10:15:10:17 | "a" | a | stdlib.go:10:15:10:17 | "a" | +| stdlib.go:12:21:12:39 | `(^\|\\n)repository=` | (^\|\\n)repository= | stdlib.go:12:21:12:39 | `(^\|\\n)repository=` | +| stdlib.go:13:21:13:24 | "ab" | ab | stdlib.go:13:21:13:24 | "ab" | +| stdlib.go:15:26:15:39 | "[so]me\|regex" | [so]me\|regex | stdlib.go:15:2:15:40 | ... := ...[0] | +| stdlib.go:15:26:15:39 | "[so]me\|regex" | [so]me\|regex | stdlib.go:15:26:15:39 | "[so]me\|regex" | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:16:2:16:3 | definition of re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:16:2:16:38 | ... = ...[0] | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:16:30:16:37 | "posix?" | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:17:2:17:3 | re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:21:2:21:3 | re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:22:2:22:3 | re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:23:2:23:3 | re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:24:2:24:3 | re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:25:2:25:3 | re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:26:2:26:3 | re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:28:2:28:3 | re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:29:2:29:3 | re | +| stdlib.go:16:30:16:37 | "posix?" | posix? | stdlib.go:31:2:31:3 | re | +| stdlib.go:18:21:18:22 | "" | | stdlib.go:18:2:18:23 | call to MustCompile | +| stdlib.go:18:21:18:22 | "" | | stdlib.go:18:21:18:22 | "" | +| stdlib.go:19:26:19:36 | "mustPosix" | mustPosix | stdlib.go:19:2:19:37 | call to MustCompilePOSIX | +| stdlib.go:19:26:19:36 | "mustPosix" | mustPosix | stdlib.go:19:26:19:36 | "mustPosix" | diff --git a/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpPattern.ql b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpPattern.ql new file mode 100644 index 00000000..98a3d95f --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpPattern.ql @@ -0,0 +1,4 @@ +import go + +from RegexpPattern rep +select rep, rep.getPattern(), rep.getAUse() diff --git a/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpReplaceFunction.expected b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpReplaceFunction.expected new file mode 100644 index 00000000..bab7f2c6 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpReplaceFunction.expected @@ -0,0 +1,6 @@ +| ReplaceAll | stdlib.go:16:30:16:37 | "posix?" | stdlib.go:21:16:21:29 | type conversion | stdlib.go:21:2:21:46 | call to ReplaceAll | +| ReplaceAllFunc | stdlib.go:16:30:16:37 | "posix?" | stdlib.go:22:20:22:33 | type conversion | stdlib.go:22:2:22:85 | call to ReplaceAllFunc | +| ReplaceAllLiteral | stdlib.go:16:30:16:37 | "posix?" | stdlib.go:23:23:23:36 | type conversion | stdlib.go:23:2:23:53 | call to ReplaceAllLiteral | +| ReplaceAllLiteralString | stdlib.go:16:30:16:37 | "posix?" | stdlib.go:24:29:24:34 | "src3" | stdlib.go:24:2:24:43 | call to ReplaceAllLiteralString | +| ReplaceAllString | stdlib.go:16:30:16:37 | "posix?" | stdlib.go:25:22:25:27 | "src4" | stdlib.go:25:2:25:36 | call to ReplaceAllString | +| ReplaceAllStringFunc | stdlib.go:16:30:16:37 | "posix?" | stdlib.go:26:26:26:31 | "src5" | stdlib.go:26:2:26:75 | call to ReplaceAllStringFunc | diff --git a/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpReplaceFunction.ql b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpReplaceFunction.ql new file mode 100644 index 00000000..bdb569fd --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Regexp/RegexpReplaceFunction.ql @@ -0,0 +1,5 @@ +import go + +from RegexpReplaceFunction rrfc, DataFlow::CallNode call +where call = rrfc.getACall() +select rrfc, rrfc.getRegexp(call), rrfc.getSource().getNode(call), rrfc.getResult().getNode(call) diff --git a/ql/test/library-tests/semmle/go/concepts/Regexp/stdlib.go b/ql/test/library-tests/semmle/go/concepts/Regexp/stdlib.go new file mode 100644 index 00000000..beb0c0c4 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Regexp/stdlib.go @@ -0,0 +1,32 @@ +package main + +import ( + "bufio" + "os" + "regexp" +) + +func main() { + regexp.Match("a", []byte("aaaa")) + file, _ := os.Open("file") + regexp.MatchReader(`(^|\n)repository=`, bufio.NewReader(file)) + regexp.MatchString("ab", "baaa") + + re, _ := regexp.Compile("[so]me|regex") + re, _ = regexp.CompilePOSIX("posix?") + re.MatchString("posi") + regexp.MustCompile("") + regexp.MustCompilePOSIX("mustPosix") + + re.ReplaceAll([]byte("src0"), []byte("rep0")) + re.ReplaceAllFunc([]byte("src1"), func(pat []byte) []byte { return []byte("rep1") }) + re.ReplaceAllLiteral([]byte("src2"), []byte("rep2")) + re.ReplaceAllLiteralString("src3", "rep3") + re.ReplaceAllString("src4", "rep4") + re.ReplaceAllStringFunc("src5", func(pat string) string { return "rep5" }) + + re.Match([]byte("some")) + re.MatchReader(bufio.NewReader(file)) + + re.String() +} diff --git a/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/SystemCommandExecution.expected b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/SystemCommandExecution.expected new file mode 100644 index 00000000..cb5a9c45 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/SystemCommandExecution.expected @@ -0,0 +1,5 @@ +| gosh.go:6:2:6:35 | call to Command | gosh.go:6:13:6:18 | "echo" | +| main.go:12:9:12:53 | call to Command | main.go:12:22:12:26 | "gcc" | +| main.go:15:8:15:69 | call to CommandContext | main.go:15:50:15:56 | "sleep" | +| main.go:18:15:18:65 | call to Command | main.go:18:28:18:36 | "example" | +| main.go:21:2:21:48 | call to StartProcess | main.go:21:18:21:21 | "go" | diff --git a/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/SystemCommandExecution.ql b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/SystemCommandExecution.ql new file mode 100644 index 00000000..49f4352d --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/SystemCommandExecution.ql @@ -0,0 +1,4 @@ +import go + +from SystemCommandExecution ex +select ex, ex.getCommandName() diff --git a/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/go.mod b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/go.mod new file mode 100644 index 00000000..0dea7f97 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/go.mod @@ -0,0 +1,5 @@ +module semmle.go.concepts.SystemCommandExecution + +go 1.12 + +require github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 diff --git a/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/gosh.go b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/gosh.go new file mode 100644 index 00000000..b5385d33 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/gosh.go @@ -0,0 +1,7 @@ +package main + +import "github.com/codeskyblue/go-sh" + +func test() { + sh.Command("echo", "hello\tworld").Run() +} diff --git a/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/main.go b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/main.go new file mode 100644 index 00000000..9a935d60 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/exec" +) + +func main() { + fmt.Println("Running command...") + cmd := exec.Command("gcc", "-o", "hello", "hello.c") + cmd.Run() + + cmd = exec.CommandContext(context.Background(), "sleep", "10000000") + cmd.Start() + + unusedcmd := exec.Command("example", "of a use of this command") + fmt.Println(unusedcmd) + + os.StartProcess("go", []string{"version"}, nil) +} diff --git a/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/LICENSE b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/LICENSE new file mode 100644 index 00000000..e06d2081 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/README.md b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/README.md new file mode 100644 index 00000000..d44b2f43 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/README.md @@ -0,0 +1,3 @@ +This is a simple stub for https://github.com/codeskyblue/go-sh, strictly for use in query testing. + +See the LICENSE file in this folder for information about the licensing of the original library. \ No newline at end of file diff --git a/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/sh.go b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/sh.go new file mode 100644 index 00000000..3f038907 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/github.com/codeskyblue/go-sh/sh.go @@ -0,0 +1,9 @@ +package sh + +type Cmd struct{} + +func (*Cmd) Run() {} + +func Command(name string, a ...string) *Cmd { + return nil +} diff --git a/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/modules.txt b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/modules.txt new file mode 100644 index 00000000..68d5ffd8 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/SystemCommandExecution/vendor/modules.txt @@ -0,0 +1,2 @@ +# github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 +github.com/codeskyblue/go-sh diff --git a/ql/test/library-tests/semmle/go/concepts/Templates/Templates.expected b/ql/test/library-tests/semmle/go/concepts/Templates/Templates.expected new file mode 100644 index 00000000..efc617f5 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Templates/Templates.expected @@ -0,0 +1,2 @@ +| main.go:26:9:26:38 | call to Execute | main.go:26:9:26:13 | templ | main.go:26:34:26:37 | data | +| main.go:32:2:32:73 | call to ExecuteTemplate | main.go:32:2:32:6 | templ | main.go:32:49:32:72 | "This is a long message" | diff --git a/ql/test/library-tests/semmle/go/concepts/Templates/Templates.ql b/ql/test/library-tests/semmle/go/concepts/Templates/Templates.ql new file mode 100644 index 00000000..dbcd0314 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Templates/Templates.ql @@ -0,0 +1,4 @@ +import go + +from TemplateInstantiation tinst +select tinst, tinst.getTemplateArgument(), tinst.getADataArgument() diff --git a/ql/test/library-tests/semmle/go/concepts/Templates/main.go b/ql/test/library-tests/semmle/go/concepts/Templates/main.go new file mode 100644 index 00000000..a45f27a7 --- /dev/null +++ b/ql/test/library-tests/semmle/go/concepts/Templates/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "os" + "text/template" +) + +func main() { + const site = ` + + + My Cool Site + + +

    {{.Name}}

    +

    {{.Message}}

    + + +` + + data := struct{ Name, Message string }{"Thomas", "Hello Thomas!"} + + templ := template.Must(template.New("letter").Parse(site)) + + err := templ.Execute(os.Stdout, data) + if err != nil { + fmt.Println("Error: ", err) + } + + templ = template.Must(template.ParseFiles("someletter", "somejs")) + templ.ExecuteTemplate(os.Stdout, "someletter", "This is a long message") +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CFG.expected b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CFG.expected new file mode 100644 index 00000000..61541811 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CFG.expected @@ -0,0 +1,4 @@ +nodes +edges +#select +| | diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CFG.ql b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CFG.ql new file mode 100644 index 00000000..144a01f2 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CFG.ql @@ -0,0 +1,10 @@ +import go + +query predicate nodes(ControlFlow::Node nd) { none() } + +query predicate edges(ControlFlow::Node pred, ControlFlow::Node succ) { + none() + // succ = pred.getASuccessor() +} + +select "" diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected new file mode 100644 index 00000000..281fd569 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.expected @@ -0,0 +1,1167 @@ +| exprs.go:0:0:0:0 | entry | exprs.go:3:1:3:29 | skip | +| exprs.go:3:1:3:29 | skip | exprs.go:5:6:5:9 | skip | +| exprs.go:5:1:5:1 | entry | exprs.go:6:6:6:6 | skip | +| exprs.go:5:1:26:1 | function declaration | exprs.go:28:6:28:10 | skip | +| exprs.go:5:6:5:9 | skip | exprs.go:5:1:26:1 | function declaration | +| exprs.go:6:6:6:6 | assignment to i | exprs.go:6:9:6:9 | assignment to j | +| exprs.go:6:6:6:6 | skip | exprs.go:6:9:6:9 | skip | +| exprs.go:6:9:6:9 | assignment to j | exprs.go:7:6:7:6 | skip | +| exprs.go:6:9:6:9 | skip | exprs.go:6:13:6:13 | 0 | +| exprs.go:6:13:6:13 | 0 | exprs.go:6:16:6:26 | ...+... | +| exprs.go:6:16:6:26 | ...+... | exprs.go:6:6:6:6 | assignment to i | +| exprs.go:7:6:7:6 | assignment to k | exprs.go:8:2:8:2 | skip | +| exprs.go:7:6:7:6 | skip | exprs.go:7:10:7:10 | i | +| exprs.go:7:10:7:10 | i | exprs.go:7:14:7:14 | 2 | +| exprs.go:7:10:7:16 | ...+... | exprs.go:7:6:7:6 | assignment to k | +| exprs.go:7:14:7:14 | 2 | exprs.go:7:16:7:16 | j | +| exprs.go:7:14:7:16 | ...*... | exprs.go:7:10:7:16 | ...+... | +| exprs.go:7:16:7:16 | j | exprs.go:7:14:7:16 | ...*... | +| exprs.go:8:2:8:2 | assignment to s | exprs.go:9:2:9:3 | skip | +| exprs.go:8:2:8:2 | skip | exprs.go:8:7:8:12 | "k = " | +| exprs.go:8:7:8:12 | "k = " | exprs.go:8:23:8:23 | k | +| exprs.go:8:7:8:24 | ...+... | exprs.go:8:2:8:2 | assignment to s | +| exprs.go:8:16:8:24 | type conversion | exprs.go:8:7:8:24 | ...+... | +| exprs.go:8:23:8:23 | k | exprs.go:8:16:8:24 | type conversion | +| exprs.go:9:2:9:3 | assignment to fn | exprs.go:10:2:10:8 | skip | +| exprs.go:9:2:9:3 | skip | exprs.go:9:8:9:61 | function literal | +| exprs.go:9:8:9:8 | entry | exprs.go:9:13:9:13 | argument corresponding to a | +| exprs.go:9:8:9:61 | function literal | exprs.go:9:2:9:3 | assignment to fn | +| exprs.go:9:13:9:13 | argument corresponding to a | exprs.go:9:13:9:13 | initialization of a | +| exprs.go:9:13:9:13 | initialization of a | exprs.go:9:16:9:16 | argument corresponding to b | +| exprs.go:9:16:9:16 | argument corresponding to b | exprs.go:9:16:9:16 | initialization of b | +| exprs.go:9:16:9:16 | initialization of b | exprs.go:9:23:9:23 | argument corresponding to z | +| exprs.go:9:23:9:23 | argument corresponding to z | exprs.go:9:23:9:23 | initialization of z | +| exprs.go:9:23:9:23 | initialization of z | exprs.go:9:48:9:48 | a | +| exprs.go:9:41:9:59 | return statement | exprs.go:9:61:9:61 | exit | +| exprs.go:9:48:9:48 | a | exprs.go:9:50:9:50 | b | +| exprs.go:9:48:9:50 | ...*... | exprs.go:9:58:9:58 | z | +| exprs.go:9:48:9:59 | ...<... | exprs.go:9:41:9:59 | return statement | +| exprs.go:9:50:9:50 | b | exprs.go:9:48:9:50 | ...*... | +| exprs.go:9:54:9:59 | type conversion | exprs.go:9:48:9:59 | ...<... | +| exprs.go:9:58:9:58 | z | exprs.go:9:54:9:59 | type conversion | +| exprs.go:10:2:10:8 | assignment to struct1 | exprs.go:11:2:11:8 | skip | +| exprs.go:10:2:10:8 | skip | exprs.go:10:13:10:32 | composite literal | +| exprs.go:10:13:10:32 | composite literal | exprs.go:10:2:10:8 | assignment to struct1 | +| exprs.go:11:2:11:8 | assignment to struct2 | exprs.go:15:2:15:8 | skip | +| exprs.go:11:2:11:8 | skip | exprs.go:11:13:14:21 | composite literal | +| exprs.go:11:13:14:21 | composite literal | exprs.go:14:4:14:4 | k | +| exprs.go:14:4:14:4 | init of k | exprs.go:14:7:14:8 | fn | +| exprs.go:14:4:14:4 | k | exprs.go:14:4:14:4 | init of k | +| exprs.go:14:7:14:8 | fn | exprs.go:14:10:14:10 | i | +| exprs.go:14:7:14:20 | call to fn | exprs.go:14:7:14:20 | init of call to fn | +| exprs.go:14:7:14:20 | call to fn | exprs.go:26:1:26:1 | exit | +| exprs.go:14:7:14:20 | init of call to fn | exprs.go:11:2:11:8 | assignment to struct2 | +| exprs.go:14:10:14:10 | i | exprs.go:14:13:14:13 | j | +| exprs.go:14:13:14:13 | j | exprs.go:14:16:14:19 | .../... | +| exprs.go:14:16:14:19 | .../... | exprs.go:14:7:14:20 | call to fn | +| exprs.go:15:2:15:8 | assignment to struct3 | exprs.go:16:2:16:5 | skip | +| exprs.go:15:2:15:8 | skip | exprs.go:15:13:15:58 | composite literal | +| exprs.go:15:13:15:58 | composite literal | exprs.go:15:35:15:41 | struct1 | +| exprs.go:15:32:15:43 | init of key-value pair | exprs.go:15:49:15:55 | struct2 | +| exprs.go:15:35:15:41 | struct1 | exprs.go:15:35:15:43 | selection of x | +| exprs.go:15:35:15:43 | selection of x | exprs.go:15:32:15:43 | init of key-value pair | +| exprs.go:15:35:15:43 | selection of x | exprs.go:26:1:26:1 | exit | +| exprs.go:15:46:15:57 | init of key-value pair | exprs.go:15:2:15:8 | assignment to struct3 | +| exprs.go:15:49:15:55 | struct2 | exprs.go:15:49:15:57 | selection of x | +| exprs.go:15:49:15:57 | selection of x | exprs.go:15:46:15:57 | init of key-value pair | +| exprs.go:15:49:15:57 | selection of x | exprs.go:26:1:26:1 | exit | +| exprs.go:16:2:16:5 | assignment to arr1 | exprs.go:17:2:17:5 | skip | +| exprs.go:16:2:16:5 | skip | exprs.go:16:10:16:26 | composite literal | +| exprs.go:16:10:16:26 | composite literal | exprs.go:16:17:16:17 | element index | +| exprs.go:16:17:16:17 | element index | exprs.go:16:17:16:23 | struct3 | +| exprs.go:16:17:16:23 | struct3 | exprs.go:16:17:16:25 | selection of x | +| exprs.go:16:17:16:25 | init of selection of x | exprs.go:16:2:16:5 | assignment to arr1 | +| exprs.go:16:17:16:25 | selection of x | exprs.go:16:17:16:25 | init of selection of x | +| exprs.go:16:17:16:25 | selection of x | exprs.go:26:1:26:1 | exit | +| exprs.go:17:2:17:5 | assignment to arr2 | exprs.go:18:2:18:4 | skip | +| exprs.go:17:2:17:5 | skip | exprs.go:17:10:17:40 | composite literal | +| exprs.go:17:10:17:40 | composite literal | exprs.go:17:19:17:19 | element index | +| exprs.go:17:19:17:19 | element index | exprs.go:17:19:17:25 | struct3 | +| exprs.go:17:19:17:25 | struct3 | exprs.go:17:19:17:27 | selection of x | +| exprs.go:17:19:17:27 | init of selection of x | exprs.go:17:30:17:30 | 2 | +| exprs.go:17:19:17:27 | selection of x | exprs.go:17:19:17:27 | init of selection of x | +| exprs.go:17:19:17:27 | selection of x | exprs.go:26:1:26:1 | exit | +| exprs.go:17:30:17:30 | 2 | exprs.go:17:33:17:36 | arr1 | +| exprs.go:17:30:17:39 | init of key-value pair | exprs.go:17:2:17:5 | assignment to arr2 | +| exprs.go:17:33:17:36 | arr1 | exprs.go:17:38:17:38 | 0 | +| exprs.go:17:33:17:39 | index expression | exprs.go:17:30:17:39 | init of key-value pair | +| exprs.go:17:33:17:39 | index expression | exprs.go:26:1:26:1 | exit | +| exprs.go:17:38:17:38 | 0 | exprs.go:17:33:17:39 | index expression | +| exprs.go:18:2:18:4 | assignment to slc | exprs.go:19:2:19:3 | skip | +| exprs.go:18:2:18:4 | skip | exprs.go:18:9:18:22 | composite literal | +| exprs.go:18:9:18:22 | composite literal | exprs.go:18:18:18:18 | element index | +| exprs.go:18:18:18:18 | element index | exprs.go:18:18:18:18 | s | +| exprs.go:18:18:18:18 | init of s | exprs.go:18:21:18:21 | element index | +| exprs.go:18:18:18:18 | s | exprs.go:18:18:18:18 | init of s | +| exprs.go:18:21:18:21 | element index | exprs.go:18:21:18:21 | s | +| exprs.go:18:21:18:21 | init of s | exprs.go:18:2:18:4 | assignment to slc | +| exprs.go:18:21:18:21 | s | exprs.go:18:21:18:21 | init of s | +| exprs.go:19:2:19:3 | assignment to mp | exprs.go:20:2:20:5 | skip | +| exprs.go:19:2:19:3 | skip | exprs.go:19:8:19:38 | composite literal | +| exprs.go:19:8:19:38 | composite literal | exprs.go:19:23:19:25 | slc | +| exprs.go:19:23:19:25 | slc | exprs.go:19:27:19:27 | 0 | +| exprs.go:19:23:19:28 | index expression | exprs.go:19:31:19:34 | arr2 | +| exprs.go:19:23:19:28 | index expression | exprs.go:26:1:26:1 | exit | +| exprs.go:19:23:19:37 | init of key-value pair | exprs.go:19:2:19:3 | assignment to mp | +| exprs.go:19:27:19:27 | 0 | exprs.go:19:23:19:28 | index expression | +| exprs.go:19:31:19:34 | arr2 | exprs.go:19:36:19:36 | 1 | +| exprs.go:19:31:19:37 | index expression | exprs.go:19:23:19:37 | init of key-value pair | +| exprs.go:19:31:19:37 | index expression | exprs.go:26:1:26:1 | exit | +| exprs.go:19:36:19:36 | 1 | exprs.go:19:31:19:37 | index expression | +| exprs.go:20:2:20:5 | assignment to slc2 | exprs.go:21:2:21:5 | skip | +| exprs.go:20:2:20:5 | skip | exprs.go:20:10:20:12 | slc | +| exprs.go:20:10:20:12 | slc | exprs.go:20:14:20:14 | 1 | +| exprs.go:20:10:20:19 | slice expression | exprs.go:20:2:20:5 | assignment to slc2 | +| exprs.go:20:10:20:19 | slice expression | exprs.go:26:1:26:1 | exit | +| exprs.go:20:14:20:14 | 1 | exprs.go:20:16:20:16 | 2 | +| exprs.go:20:16:20:16 | 2 | exprs.go:20:18:20:18 | 3 | +| exprs.go:20:18:20:18 | 3 | exprs.go:20:10:20:19 | slice expression | +| exprs.go:21:2:21:5 | assignment to slc3 | exprs.go:22:2:22:5 | skip | +| exprs.go:21:2:21:5 | skip | exprs.go:21:10:21:13 | slc2 | +| exprs.go:21:10:21:13 | slc2 | exprs.go:21:10:21:19 | 0 | +| exprs.go:21:10:21:19 | 0 | exprs.go:21:16:21:16 | 2 | +| exprs.go:21:10:21:19 | slice expression | exprs.go:21:2:21:5 | assignment to slc3 | +| exprs.go:21:10:21:19 | slice expression | exprs.go:26:1:26:1 | exit | +| exprs.go:21:16:21:16 | 2 | exprs.go:21:18:21:18 | 3 | +| exprs.go:21:18:21:18 | 3 | exprs.go:21:10:21:19 | slice expression | +| exprs.go:22:2:22:5 | assignment to slc4 | exprs.go:23:2:23:5 | skip | +| exprs.go:22:2:22:5 | skip | exprs.go:22:10:22:13 | slc3 | +| exprs.go:22:10:22:13 | slc3 | exprs.go:22:15:22:15 | 0 | +| exprs.go:22:10:22:18 | cap | exprs.go:22:10:22:18 | slice expression | +| exprs.go:22:10:22:18 | slice expression | exprs.go:22:2:22:5 | assignment to slc4 | +| exprs.go:22:10:22:18 | slice expression | exprs.go:26:1:26:1 | exit | +| exprs.go:22:15:22:15 | 0 | exprs.go:22:17:22:17 | 2 | +| exprs.go:22:17:22:17 | 2 | exprs.go:22:10:22:18 | cap | +| exprs.go:23:2:23:5 | assignment to slc5 | exprs.go:24:2:24:5 | skip | +| exprs.go:23:2:23:5 | skip | exprs.go:23:10:23:13 | slc4 | +| exprs.go:23:10:23:13 | slc4 | exprs.go:23:15:23:15 | 0 | +| exprs.go:23:10:23:17 | cap | exprs.go:23:10:23:17 | slice expression | +| exprs.go:23:10:23:17 | len | exprs.go:23:10:23:17 | cap | +| exprs.go:23:10:23:17 | slice expression | exprs.go:23:2:23:5 | assignment to slc5 | +| exprs.go:23:10:23:17 | slice expression | exprs.go:26:1:26:1 | exit | +| exprs.go:23:15:23:15 | 0 | exprs.go:23:10:23:17 | len | +| exprs.go:24:2:24:5 | assignment to slc6 | exprs.go:25:9:25:34 | composite literal | +| exprs.go:24:2:24:5 | skip | exprs.go:24:10:24:13 | slc5 | +| exprs.go:24:10:24:13 | slc5 | exprs.go:24:10:24:17 | 0 | +| exprs.go:24:10:24:17 | 0 | exprs.go:24:16:24:16 | 2 | +| exprs.go:24:10:24:17 | cap | exprs.go:24:10:24:17 | slice expression | +| exprs.go:24:10:24:17 | slice expression | exprs.go:24:2:24:5 | assignment to slc6 | +| exprs.go:24:10:24:17 | slice expression | exprs.go:26:1:26:1 | exit | +| exprs.go:24:16:24:16 | 2 | exprs.go:24:10:24:17 | cap | +| exprs.go:25:2:25:34 | return statement | exprs.go:26:1:26:1 | exit | +| exprs.go:25:9:25:34 | composite literal | exprs.go:25:15:25:16 | mp | +| exprs.go:25:15:25:16 | mp | exprs.go:25:18:25:18 | s | +| exprs.go:25:15:25:19 | index expression | exprs.go:25:15:25:19 | init of index expression | +| exprs.go:25:15:25:19 | index expression | exprs.go:26:1:26:1 | exit | +| exprs.go:25:15:25:19 | init of index expression | exprs.go:25:22:25:24 | len | +| exprs.go:25:18:25:18 | s | exprs.go:25:15:25:19 | index expression | +| exprs.go:25:22:25:24 | len | exprs.go:25:26:25:29 | slc6 | +| exprs.go:25:22:25:33 | call to len | exprs.go:25:22:25:33 | init of call to len | +| exprs.go:25:22:25:33 | init of call to len | exprs.go:25:2:25:34 | return statement | +| exprs.go:25:26:25:29 | slc6 | exprs.go:25:31:25:31 | 0 | +| exprs.go:25:26:25:32 | index expression | exprs.go:25:22:25:33 | call to len | +| exprs.go:25:26:25:32 | index expression | exprs.go:26:1:26:1 | exit | +| exprs.go:25:31:25:31 | 0 | exprs.go:25:26:25:32 | index expression | +| exprs.go:28:1:28:1 | entry | exprs.go:28:12:28:14 | argument corresponding to arg | +| exprs.go:28:1:30:1 | function declaration | exprs.go:32:6:32:10 | skip | +| exprs.go:28:6:28:10 | skip | exprs.go:28:1:30:1 | function declaration | +| exprs.go:28:12:28:14 | argument corresponding to arg | exprs.go:28:12:28:14 | initialization of arg | +| exprs.go:28:12:28:14 | initialization of arg | exprs.go:29:9:29:11 | arg | +| exprs.go:29:2:29:21 | return statement | exprs.go:30:1:30:1 | exit | +| exprs.go:29:9:29:11 | arg | exprs.go:29:9:29:19 | type assertion | +| exprs.go:29:9:29:19 | type assertion | exprs.go:29:9:29:21 | selection of x | +| exprs.go:29:9:29:19 | type assertion | exprs.go:30:1:30:1 | exit | +| exprs.go:29:9:29:21 | selection of x | exprs.go:29:2:29:21 | return statement | +| exprs.go:29:9:29:21 | selection of x | exprs.go:30:1:30:1 | exit | +| exprs.go:32:1:32:1 | entry | exprs.go:32:12:32:14 | argument corresponding to arg | +| exprs.go:32:1:37:1 | function declaration | exprs.go:39:6:39:10 | skip | +| exprs.go:32:6:32:10 | skip | exprs.go:32:1:37:1 | function declaration | +| exprs.go:32:12:32:14 | argument corresponding to arg | exprs.go:32:12:32:14 | initialization of arg | +| exprs.go:32:12:32:14 | initialization of arg | exprs.go:33:5:33:5 | skip | +| exprs.go:33:5:33:5 | assignment to p | exprs.go:33:5:33:24 | ... := ...[1] | +| exprs.go:33:5:33:5 | skip | exprs.go:33:8:33:9 | skip | +| exprs.go:33:5:33:24 | ... := ...[0] | exprs.go:33:5:33:5 | assignment to p | +| exprs.go:33:5:33:24 | ... := ...[1] | exprs.go:33:8:33:9 | assignment to ok | +| exprs.go:33:8:33:9 | assignment to ok | exprs.go:33:27:33:28 | ok | +| exprs.go:33:8:33:9 | skip | exprs.go:33:14:33:16 | arg | +| exprs.go:33:14:33:16 | arg | exprs.go:33:14:33:24 | type assertion | +| exprs.go:33:14:33:24 | type assertion | exprs.go:33:5:33:24 | ... := ...[0] | +| exprs.go:33:27:33:28 | ok | exprs.go:33:28:33:28 | ok is false | +| exprs.go:33:27:33:28 | ok | exprs.go:33:28:33:28 | ok is true | +| exprs.go:33:28:33:28 | ok is false | exprs.go:36:9:36:10 | -... | +| exprs.go:33:28:33:28 | ok is true | exprs.go:34:10:34:10 | p | +| exprs.go:34:3:34:12 | return statement | exprs.go:37:1:37:1 | exit | +| exprs.go:34:10:34:10 | p | exprs.go:34:10:34:12 | selection of x | +| exprs.go:34:10:34:12 | selection of x | exprs.go:34:3:34:12 | return statement | +| exprs.go:34:10:34:12 | selection of x | exprs.go:37:1:37:1 | exit | +| exprs.go:36:2:36:10 | return statement | exprs.go:37:1:37:1 | exit | +| exprs.go:36:9:36:10 | -... | exprs.go:36:2:36:10 | return statement | +| exprs.go:39:1:39:1 | entry | exprs.go:39:12:39:14 | argument corresponding to arg | +| exprs.go:39:1:47:1 | function declaration | exprs.go:49:6:49:8 | skip | +| exprs.go:39:6:39:10 | skip | exprs.go:39:1:47:1 | function declaration | +| exprs.go:39:12:39:14 | argument corresponding to arg | exprs.go:39:12:39:14 | initialization of arg | +| exprs.go:39:12:39:14 | initialization of arg | exprs.go:40:6:40:6 | skip | +| exprs.go:40:6:40:6 | assignment to p | exprs.go:41:6:41:7 | skip | +| exprs.go:40:6:40:6 | skip | exprs.go:40:6:40:6 | zero value for p | +| exprs.go:40:6:40:6 | zero value for p | exprs.go:40:6:40:6 | assignment to p | +| exprs.go:41:6:41:7 | assignment to ok | exprs.go:42:2:42:2 | skip | +| exprs.go:41:6:41:7 | skip | exprs.go:41:6:41:7 | zero value for ok | +| exprs.go:41:6:41:7 | zero value for ok | exprs.go:41:6:41:7 | assignment to ok | +| exprs.go:42:2:42:2 | assignment to p | exprs.go:42:2:42:20 | ... = ...[1] | +| exprs.go:42:2:42:2 | skip | exprs.go:42:5:42:6 | skip | +| exprs.go:42:2:42:20 | ... = ...[0] | exprs.go:42:2:42:2 | assignment to p | +| exprs.go:42:2:42:20 | ... = ...[1] | exprs.go:42:5:42:6 | assignment to ok | +| exprs.go:42:5:42:6 | assignment to ok | exprs.go:43:5:43:6 | ok | +| exprs.go:42:5:42:6 | skip | exprs.go:42:10:42:12 | arg | +| exprs.go:42:10:42:12 | arg | exprs.go:42:10:42:20 | type assertion | +| exprs.go:42:10:42:20 | type assertion | exprs.go:42:2:42:20 | ... = ...[0] | +| exprs.go:43:5:43:6 | ok | exprs.go:43:6:43:6 | ok is false | +| exprs.go:43:5:43:6 | ok | exprs.go:43:6:43:6 | ok is true | +| exprs.go:43:6:43:6 | ok is false | exprs.go:46:9:46:10 | -... | +| exprs.go:43:6:43:6 | ok is true | exprs.go:44:10:44:10 | p | +| exprs.go:44:3:44:12 | return statement | exprs.go:47:1:47:1 | exit | +| exprs.go:44:10:44:10 | p | exprs.go:44:10:44:12 | selection of x | +| exprs.go:44:10:44:12 | selection of x | exprs.go:44:3:44:12 | return statement | +| exprs.go:44:10:44:12 | selection of x | exprs.go:47:1:47:1 | exit | +| exprs.go:46:2:46:10 | return statement | exprs.go:47:1:47:1 | exit | +| exprs.go:46:9:46:10 | -... | exprs.go:46:2:46:10 | return statement | +| exprs.go:49:1:49:1 | entry | exprs.go:49:10:49:11 | argument corresponding to xs | +| exprs.go:49:1:54:1 | function declaration | exprs.go:56:6:56:9 | skip | +| exprs.go:49:6:49:8 | skip | exprs.go:49:1:54:1 | function declaration | +| exprs.go:49:10:49:11 | argument corresponding to xs | exprs.go:49:10:49:11 | initialization of xs | +| exprs.go:49:10:49:11 | initialization of xs | exprs.go:49:21:49:23 | zero value for res | +| exprs.go:49:21:49:23 | implicit read of res | exprs.go:54:1:54:1 | exit | +| exprs.go:49:21:49:23 | initialization of res | exprs.go:50:6:50:6 | skip | +| exprs.go:49:21:49:23 | zero value for res | exprs.go:49:21:49:23 | initialization of res | +| exprs.go:50:6:50:6 | assignment to i | exprs.go:50:14:50:14 | i | +| exprs.go:50:6:50:6 | skip | exprs.go:50:11:50:11 | 0 | +| exprs.go:50:11:50:11 | 0 | exprs.go:50:6:50:6 | assignment to i | +| exprs.go:50:14:50:14 | i | exprs.go:50:18:50:20 | len | +| exprs.go:50:14:50:24 | ...<... | exprs.go:50:24:50:24 | ...<... is false | +| exprs.go:50:14:50:24 | ...<... | exprs.go:50:24:50:24 | ...<... is true | +| exprs.go:50:18:50:20 | len | exprs.go:50:22:50:23 | xs | +| exprs.go:50:18:50:24 | call to len | exprs.go:50:14:50:24 | ...<... | +| exprs.go:50:22:50:23 | xs | exprs.go:50:18:50:24 | call to len | +| exprs.go:50:24:50:24 | ...<... is false | exprs.go:53:2:53:7 | return statement | +| exprs.go:50:24:50:24 | ...<... is true | exprs.go:51:3:51:5 | res | +| exprs.go:50:27:50:27 | i | exprs.go:50:27:50:29 | 1 | +| exprs.go:50:27:50:29 | 1 | exprs.go:50:27:50:29 | rhs of increment statement | +| exprs.go:50:27:50:29 | increment statement | exprs.go:50:14:50:14 | i | +| exprs.go:50:27:50:29 | rhs of increment statement | exprs.go:50:27:50:29 | increment statement | +| exprs.go:51:3:51:5 | assignment to res | exprs.go:50:27:50:27 | i | +| exprs.go:51:3:51:5 | res | exprs.go:51:10:51:11 | xs | +| exprs.go:51:3:51:14 | ... += ... | exprs.go:51:3:51:5 | assignment to res | +| exprs.go:51:10:51:11 | xs | exprs.go:51:13:51:13 | i | +| exprs.go:51:10:51:14 | index expression | exprs.go:51:3:51:14 | ... += ... | +| exprs.go:51:10:51:14 | index expression | exprs.go:54:1:54:1 | exit | +| exprs.go:51:13:51:13 | i | exprs.go:51:10:51:14 | index expression | +| exprs.go:53:2:53:7 | return statement | exprs.go:49:21:49:23 | implicit read of res | +| exprs.go:56:1:56:1 | entry | exprs.go:56:11:56:12 | argument corresponding to xs | +| exprs.go:56:1:58:1 | function declaration | exprs.go:60:6:60:9 | skip | +| exprs.go:56:6:56:9 | skip | exprs.go:56:1:58:1 | function declaration | +| exprs.go:56:11:56:12 | argument corresponding to xs | exprs.go:56:11:56:12 | initialization of xs | +| exprs.go:56:11:56:12 | initialization of xs | exprs.go:57:9:57:11 | sum | +| exprs.go:57:2:57:15 | return statement | exprs.go:58:1:58:1 | exit | +| exprs.go:57:9:57:11 | sum | exprs.go:57:13:57:14 | xs | +| exprs.go:57:9:57:15 | call to sum | exprs.go:57:2:57:15 | return statement | +| exprs.go:57:9:57:15 | call to sum | exprs.go:58:1:58:1 | exit | +| exprs.go:57:13:57:14 | xs | exprs.go:57:9:57:15 | call to sum | +| exprs.go:60:1:60:1 | entry | exprs.go:61:9:61:22 | composite literal | +| exprs.go:60:1:62:1 | function declaration | exprs.go:64:5:64:5 | skip | +| exprs.go:60:6:60:9 | skip | exprs.go:60:1:62:1 | function declaration | +| exprs.go:61:2:61:22 | return statement | exprs.go:62:1:62:1 | exit | +| exprs.go:61:9:61:22 | composite literal | exprs.go:61:15:61:15 | element index | +| exprs.go:61:15:61:15 | 1 | exprs.go:61:15:61:15 | init of 1 | +| exprs.go:61:15:61:15 | element index | exprs.go:61:15:61:15 | 1 | +| exprs.go:61:15:61:15 | init of 1 | exprs.go:61:18:61:18 | element index | +| exprs.go:61:18:61:18 | 2 | exprs.go:61:18:61:18 | init of 2 | +| exprs.go:61:18:61:18 | element index | exprs.go:61:18:61:18 | 2 | +| exprs.go:61:18:61:18 | init of 2 | exprs.go:61:21:61:21 | element index | +| exprs.go:61:21:61:21 | 3 | exprs.go:61:21:61:21 | init of 3 | +| exprs.go:61:21:61:21 | element index | exprs.go:61:21:61:21 | 3 | +| exprs.go:61:21:61:21 | init of 3 | exprs.go:61:2:61:22 | return statement | +| exprs.go:64:5:64:5 | assignment to s | exprs.go:65:5:65:6 | skip | +| exprs.go:64:5:64:5 | skip | exprs.go:64:9:64:11 | sum | +| exprs.go:64:9:64:11 | sum | exprs.go:64:13:64:16 | ints | +| exprs.go:64:9:64:19 | call to sum | exprs.go:0:0:0:0 | exit | +| exprs.go:64:9:64:19 | call to sum | exprs.go:64:5:64:5 | assignment to s | +| exprs.go:64:13:64:16 | ints | exprs.go:64:13:64:18 | call to ints | +| exprs.go:64:13:64:18 | call to ints | exprs.go:0:0:0:0 | exit | +| exprs.go:64:13:64:18 | call to ints | exprs.go:64:9:64:19 | call to sum | +| exprs.go:65:5:65:6 | assignment to s2 | exprs.go:67:6:67:8 | skip | +| exprs.go:65:5:65:6 | skip | exprs.go:65:10:65:13 | sum2 | +| exprs.go:65:10:65:13 | sum2 | exprs.go:65:15:65:18 | ints | +| exprs.go:65:10:65:24 | call to sum2 | exprs.go:0:0:0:0 | exit | +| exprs.go:65:10:65:24 | call to sum2 | exprs.go:65:5:65:6 | assignment to s2 | +| exprs.go:65:15:65:18 | ints | exprs.go:65:15:65:20 | call to ints | +| exprs.go:65:15:65:20 | call to ints | exprs.go:0:0:0:0 | exit | +| exprs.go:65:15:65:20 | call to ints | exprs.go:65:10:65:24 | call to sum2 | +| exprs.go:67:1:67:1 | entry | exprs.go:67:10:67:10 | argument corresponding to x | +| exprs.go:67:1:69:1 | function declaration | exprs.go:71:6:71:8 | skip | +| exprs.go:67:6:67:8 | skip | exprs.go:67:1:69:1 | function declaration | +| exprs.go:67:10:67:10 | argument corresponding to x | exprs.go:67:10:67:10 | initialization of x | +| exprs.go:67:10:67:10 | initialization of x | exprs.go:67:13:67:13 | argument corresponding to y | +| exprs.go:67:13:67:13 | argument corresponding to y | exprs.go:67:13:67:13 | initialization of y | +| exprs.go:67:13:67:13 | initialization of y | exprs.go:68:9:68:9 | x | +| exprs.go:68:2:68:13 | return statement | exprs.go:69:1:69:1 | exit | +| exprs.go:68:9:68:9 | x | exprs.go:68:13:68:13 | y | +| exprs.go:68:9:68:13 | ...+... | exprs.go:68:2:68:13 | return statement | +| exprs.go:68:13:68:13 | y | exprs.go:68:9:68:13 | ...+... | +| exprs.go:71:1:71:1 | entry | exprs.go:72:9:72:9 | 1 | +| exprs.go:71:1:73:1 | function declaration | exprs.go:75:5:75:6 | skip | +| exprs.go:71:6:71:8 | skip | exprs.go:71:1:73:1 | function declaration | +| exprs.go:72:2:72:12 | return statement | exprs.go:73:1:73:1 | exit | +| exprs.go:72:9:72:9 | 1 | exprs.go:72:12:72:12 | 2 | +| exprs.go:72:12:72:12 | 2 | exprs.go:72:2:72:12 | return statement | +| exprs.go:75:5:75:6 | assignment to s3 | exprs.go:77:6:77:10 | skip | +| exprs.go:75:5:75:6 | skip | exprs.go:75:10:75:12 | add | +| exprs.go:75:10:75:12 | add | exprs.go:75:14:75:16 | gen | +| exprs.go:75:10:75:19 | call to add | exprs.go:0:0:0:0 | exit | +| exprs.go:75:10:75:19 | call to add | exprs.go:75:5:75:6 | assignment to s3 | +| exprs.go:75:10:75:19 | call to add[0] | exprs.go:75:10:75:19 | call to add[1] | +| exprs.go:75:10:75:19 | call to add[1] | exprs.go:75:10:75:19 | call to add | +| exprs.go:75:14:75:16 | gen | exprs.go:75:14:75:18 | call to gen | +| exprs.go:75:14:75:18 | call to gen | exprs.go:0:0:0:0 | exit | +| exprs.go:75:14:75:18 | call to gen | exprs.go:75:10:75:19 | call to add[0] | +| exprs.go:77:1:77:1 | entry | exprs.go:77:12:77:12 | argument corresponding to x | +| exprs.go:77:1:79:1 | function declaration | exprs.go:81:6:81:16 | skip | +| exprs.go:77:6:77:10 | skip | exprs.go:77:1:79:1 | function declaration | +| exprs.go:77:12:77:12 | argument corresponding to x | exprs.go:77:12:77:12 | initialization of x | +| exprs.go:77:12:77:12 | initialization of x | exprs.go:77:15:77:15 | argument corresponding to y | +| exprs.go:77:15:77:15 | argument corresponding to y | exprs.go:77:15:77:15 | initialization of y | +| exprs.go:77:15:77:15 | initialization of y | exprs.go:77:18:77:18 | argument corresponding to z | +| exprs.go:77:18:77:18 | argument corresponding to z | exprs.go:77:18:77:18 | initialization of z | +| exprs.go:77:18:77:18 | initialization of z | exprs.go:78:11:78:11 | x | +| exprs.go:78:2:78:22 | return statement | exprs.go:79:1:79:1 | exit | +| exprs.go:78:9:78:17 | !... | exprs.go:78:17:78:17 | !... is false | +| exprs.go:78:9:78:17 | !... | exprs.go:78:17:78:17 | !... is true | +| exprs.go:78:9:78:22 | ...\|\|... | exprs.go:78:2:78:22 | return statement | +| exprs.go:78:11:78:11 | x | exprs.go:78:11:78:11 | x is false | +| exprs.go:78:11:78:11 | x | exprs.go:78:11:78:11 | x is true | +| exprs.go:78:11:78:11 | x is false | exprs.go:78:11:78:16 | ...&&... | +| exprs.go:78:11:78:11 | x is true | exprs.go:78:16:78:16 | y | +| exprs.go:78:11:78:16 | ...&&... | exprs.go:78:9:78:17 | !... | +| exprs.go:78:16:78:16 | y | exprs.go:78:11:78:16 | ...&&... | +| exprs.go:78:17:78:17 | !... is false | exprs.go:78:22:78:22 | z | +| exprs.go:78:17:78:17 | !... is true | exprs.go:78:9:78:22 | ...\|\|... | +| exprs.go:78:22:78:22 | z | exprs.go:78:9:78:22 | ...\|\|... | +| exprs.go:81:1:81:1 | entry | exprs.go:81:18:81:19 | argument corresponding to ch | +| exprs.go:81:1:87:1 | function declaration | exprs.go:89:7:89:9 | skip | +| exprs.go:81:6:81:16 | skip | exprs.go:81:1:87:1 | function declaration | +| exprs.go:81:18:81:19 | argument corresponding to ch | exprs.go:81:18:81:19 | initialization of ch | +| exprs.go:81:18:81:19 | initialization of ch | exprs.go:82:2:82:4 | skip | +| exprs.go:82:2:82:4 | assignment to val | exprs.go:82:2:82:16 | ... := ...[1] | +| exprs.go:82:2:82:4 | skip | exprs.go:82:7:82:8 | skip | +| exprs.go:82:2:82:16 | ... := ...[0] | exprs.go:82:2:82:4 | assignment to val | +| exprs.go:82:2:82:16 | ... := ...[1] | exprs.go:82:7:82:8 | assignment to ok | +| exprs.go:82:7:82:8 | assignment to ok | exprs.go:83:5:83:6 | ok | +| exprs.go:82:7:82:8 | skip | exprs.go:82:15:82:16 | ch | +| exprs.go:82:13:82:16 | <-... | exprs.go:82:2:82:16 | ... := ...[0] | +| exprs.go:82:15:82:16 | ch | exprs.go:82:13:82:16 | <-... | +| exprs.go:83:5:83:6 | ok | exprs.go:83:6:83:6 | ok is false | +| exprs.go:83:5:83:6 | ok | exprs.go:83:6:83:6 | ok is true | +| exprs.go:83:6:83:6 | ok is false | exprs.go:86:2:86:6 | panic | +| exprs.go:83:6:83:6 | ok is true | exprs.go:84:10:84:12 | val | +| exprs.go:84:3:84:12 | return statement | exprs.go:87:1:87:1 | exit | +| exprs.go:84:10:84:12 | val | exprs.go:84:3:84:12 | return statement | +| exprs.go:86:2:86:6 | panic | exprs.go:86:8:86:17 | "No value" | +| exprs.go:86:2:86:18 | call to panic | exprs.go:87:1:87:1 | exit | +| exprs.go:86:8:86:17 | "No value" | exprs.go:86:2:86:18 | call to panic | +| exprs.go:89:7:89:9 | assignment to one | exprs.go:91:5:91:5 | skip | +| exprs.go:89:7:89:9 | skip | exprs.go:89:13:89:13 | 1 | +| exprs.go:89:13:89:13 | 1 | exprs.go:89:7:89:9 | assignment to one | +| exprs.go:91:5:91:5 | assignment to a | exprs.go:93:6:93:11 | skip | +| exprs.go:91:5:91:5 | skip | exprs.go:91:9:91:25 | composite literal | +| exprs.go:91:9:91:25 | composite literal | exprs.go:91:15:91:21 | ...+... | +| exprs.go:91:15:91:21 | ...+... | exprs.go:91:24:91:24 | 2 | +| exprs.go:91:15:91:24 | init of key-value pair | exprs.go:91:5:91:5 | assignment to a | +| exprs.go:91:24:91:24 | 2 | exprs.go:91:15:91:24 | init of key-value pair | +| exprs.go:93:1:93:1 | entry | exprs.go:93:13:93:13 | argument corresponding to x | +| exprs.go:93:1:95:1 | function declaration | exprs.go:0:0:0:0 | exit | +| exprs.go:93:6:93:11 | skip | exprs.go:93:1:95:1 | function declaration | +| exprs.go:93:13:93:13 | argument corresponding to x | exprs.go:93:13:93:13 | initialization of x | +| exprs.go:93:13:93:13 | initialization of x | exprs.go:93:16:93:16 | argument corresponding to y | +| exprs.go:93:16:93:16 | argument corresponding to y | exprs.go:93:16:93:16 | initialization of y | +| exprs.go:93:16:93:16 | initialization of y | exprs.go:93:19:93:19 | argument corresponding to z | +| exprs.go:93:19:93:19 | argument corresponding to z | exprs.go:93:19:93:19 | initialization of z | +| exprs.go:93:19:93:19 | initialization of z | exprs.go:94:10:94:10 | x | +| exprs.go:94:2:94:21 | return statement | exprs.go:95:1:95:1 | exit | +| exprs.go:94:9:94:21 | ...\|\|... | exprs.go:94:2:94:21 | return statement | +| exprs.go:94:10:94:10 | x | exprs.go:94:10:94:10 | x is false | +| exprs.go:94:10:94:10 | x | exprs.go:94:10:94:10 | x is true | +| exprs.go:94:10:94:10 | x is false | exprs.go:94:16:94:16 | (...) is false | +| exprs.go:94:10:94:10 | x is true | exprs.go:94:15:94:15 | y | +| exprs.go:94:15:94:15 | y | exprs.go:94:16:94:16 | (...) is false | +| exprs.go:94:15:94:15 | y | exprs.go:94:16:94:16 | (...) is true | +| exprs.go:94:16:94:16 | (...) is false | exprs.go:94:21:94:21 | z | +| exprs.go:94:16:94:16 | (...) is true | exprs.go:94:9:94:21 | ...\|\|... | +| exprs.go:94:21:94:21 | z | exprs.go:94:9:94:21 | ...\|\|... | +| hello.go:0:0:0:0 | entry | hello.go:3:1:3:12 | skip | +| hello.go:3:1:3:12 | skip | hello.go:5:7:5:13 | skip | +| hello.go:5:7:5:13 | assignment to message | hello.go:7:6:7:13 | skip | +| hello.go:5:7:5:13 | skip | hello.go:5:17:5:31 | "Hello, world!" | +| hello.go:5:17:5:31 | "Hello, world!" | hello.go:5:7:5:13 | assignment to message | +| hello.go:7:1:7:1 | entry | hello.go:8:2:8:12 | selection of Println | +| hello.go:7:1:9:1 | function declaration | hello.go:0:0:0:0 | exit | +| hello.go:7:6:7:13 | skip | hello.go:7:1:9:1 | function declaration | +| hello.go:8:2:8:12 | selection of Println | hello.go:8:14:8:20 | message | +| hello.go:8:2:8:21 | call to Println | hello.go:9:1:9:1 | exit | +| hello.go:8:14:8:20 | message | hello.go:8:2:8:21 | call to Println | +| main.go:0:0:0:0 | entry | main.go:3:1:6:1 | skip | +| main.go:3:1:6:1 | skip | main.go:8:6:8:9 | skip | +| main.go:8:1:8:1 | entry | main.go:9:9:9:20 | selection of Float64 | +| main.go:8:1:10:1 | function declaration | main.go:12:6:12:9 | skip | +| main.go:8:6:8:9 | skip | main.go:8:1:10:1 | function declaration | +| main.go:9:2:9:29 | return statement | main.go:10:1:10:1 | exit | +| main.go:9:9:9:20 | selection of Float64 | main.go:9:9:9:22 | call to Float64 | +| main.go:9:9:9:22 | call to Float64 | main.go:9:27:9:29 | 0.5 | +| main.go:9:9:9:22 | call to Float64 | main.go:10:1:10:1 | exit | +| main.go:9:9:9:29 | ...>=... | main.go:9:2:9:29 | return statement | +| main.go:9:27:9:29 | 0.5 | main.go:9:9:9:29 | ...>=... | +| main.go:12:1:12:1 | entry | main.go:13:6:13:6 | skip | +| main.go:12:1:24:1 | function declaration | main.go:26:6:26:8 | skip | +| main.go:12:6:12:9 | skip | main.go:12:1:24:1 | function declaration | +| main.go:13:6:13:6 | assignment to x | main.go:14:2:14:2 | skip | +| main.go:13:6:13:6 | skip | main.go:13:6:13:6 | zero value for x | +| main.go:13:6:13:6 | zero value for x | main.go:13:6:13:6 | assignment to x | +| main.go:14:2:14:2 | assignment to y | main.go:15:2:15:10 | selection of Print | +| main.go:14:2:14:2 | skip | main.go:14:7:14:8 | 23 | +| main.go:14:7:14:8 | 23 | main.go:14:2:14:2 | assignment to y | +| main.go:15:2:15:10 | selection of Print | main.go:15:12:15:12 | x | +| main.go:15:2:15:16 | call to Print | main.go:16:5:16:8 | cond | +| main.go:15:2:15:16 | call to Print | main.go:24:1:24:1 | exit | +| main.go:15:12:15:12 | x | main.go:15:15:15:15 | y | +| main.go:15:15:15:15 | y | main.go:15:2:15:16 | call to Print | +| main.go:16:5:16:8 | cond | main.go:16:5:16:10 | call to cond | +| main.go:16:5:16:10 | call to cond | main.go:16:10:16:10 | call to cond is false | +| main.go:16:5:16:10 | call to cond | main.go:16:10:16:10 | call to cond is true | +| main.go:16:5:16:10 | call to cond | main.go:24:1:24:1 | exit | +| main.go:16:10:16:10 | call to cond is false | main.go:19:2:19:10 | selection of Print | +| main.go:16:10:16:10 | call to cond is true | main.go:17:3:17:3 | y | +| main.go:17:3:17:3 | assignment to y | main.go:19:2:19:10 | selection of Print | +| main.go:17:3:17:3 | y | main.go:17:8:17:9 | 19 | +| main.go:17:3:17:9 | ... += ... | main.go:17:3:17:3 | assignment to y | +| main.go:17:8:17:9 | 19 | main.go:17:3:17:9 | ... += ... | +| main.go:19:2:19:10 | selection of Print | main.go:19:12:19:12 | x | +| main.go:19:2:19:16 | call to Print | main.go:20:5:20:8 | cond | +| main.go:19:2:19:16 | call to Print | main.go:24:1:24:1 | exit | +| main.go:19:12:19:12 | x | main.go:19:15:19:15 | y | +| main.go:19:15:19:15 | y | main.go:19:2:19:16 | call to Print | +| main.go:20:5:20:8 | cond | main.go:20:5:20:10 | call to cond | +| main.go:20:5:20:10 | call to cond | main.go:20:10:20:10 | call to cond is false | +| main.go:20:5:20:10 | call to cond | main.go:20:10:20:10 | call to cond is true | +| main.go:20:5:20:10 | call to cond | main.go:24:1:24:1 | exit | +| main.go:20:10:20:10 | call to cond is false | main.go:23:2:23:10 | selection of Print | +| main.go:20:10:20:10 | call to cond is true | main.go:21:3:21:3 | skip | +| main.go:21:3:21:3 | assignment to x | main.go:23:2:23:10 | selection of Print | +| main.go:21:3:21:3 | skip | main.go:21:7:21:7 | y | +| main.go:21:7:21:7 | y | main.go:21:3:21:3 | assignment to x | +| main.go:23:2:23:10 | selection of Print | main.go:23:12:23:12 | x | +| main.go:23:2:23:16 | call to Print | main.go:24:1:24:1 | exit | +| main.go:23:12:23:12 | x | main.go:23:15:23:15 | y | +| main.go:23:15:23:15 | y | main.go:23:2:23:16 | call to Print | +| main.go:26:1:26:1 | entry | main.go:26:10:26:10 | argument corresponding to x | +| main.go:26:1:32:1 | function declaration | main.go:34:6:34:9 | skip | +| main.go:26:6:26:8 | skip | main.go:26:1:32:1 | function declaration | +| main.go:26:10:26:10 | argument corresponding to x | main.go:26:10:26:10 | initialization of x | +| main.go:26:10:26:10 | initialization of x | main.go:27:2:27:2 | skip | +| main.go:27:2:27:2 | assignment to a | main.go:27:5:27:5 | assignment to b | +| main.go:27:2:27:2 | skip | main.go:27:5:27:5 | skip | +| main.go:27:5:27:5 | assignment to b | main.go:28:5:28:8 | cond | +| main.go:27:5:27:5 | skip | main.go:27:10:27:10 | x | +| main.go:27:10:27:10 | x | main.go:27:13:27:13 | 0 | +| main.go:27:13:27:13 | 0 | main.go:27:2:27:2 | assignment to a | +| main.go:28:5:28:8 | cond | main.go:28:5:28:10 | call to cond | +| main.go:28:5:28:10 | call to cond | main.go:28:10:28:10 | call to cond is false | +| main.go:28:5:28:10 | call to cond | main.go:28:10:28:10 | call to cond is true | +| main.go:28:5:28:10 | call to cond | main.go:32:1:32:1 | exit | +| main.go:28:10:28:10 | call to cond is false | main.go:31:9:31:9 | a | +| main.go:28:10:28:10 | call to cond is true | main.go:29:3:29:3 | skip | +| main.go:29:3:29:3 | assignment to a | main.go:29:6:29:6 | assignment to b | +| main.go:29:3:29:3 | skip | main.go:29:6:29:6 | skip | +| main.go:29:6:29:6 | assignment to b | main.go:31:9:31:9 | a | +| main.go:29:6:29:6 | skip | main.go:29:10:29:10 | b | +| main.go:29:10:29:10 | b | main.go:29:13:29:13 | a | +| main.go:29:13:29:13 | a | main.go:29:3:29:3 | assignment to a | +| main.go:31:2:31:12 | return statement | main.go:32:1:32:1 | exit | +| main.go:31:9:31:9 | a | main.go:31:12:31:12 | b | +| main.go:31:12:31:12 | b | main.go:31:2:31:12 | return statement | +| main.go:34:1:34:1 | entry | main.go:34:11:34:11 | argument corresponding to x | +| main.go:34:1:36:1 | function declaration | main.go:38:6:38:8 | skip | +| main.go:34:6:34:9 | skip | main.go:34:1:36:1 | function declaration | +| main.go:34:11:34:11 | argument corresponding to x | main.go:34:11:34:11 | initialization of x | +| main.go:34:11:34:11 | initialization of x | main.go:35:3:35:3 | x | +| main.go:35:2:35:3 | assignment to star expression | main.go:36:1:36:1 | exit | +| main.go:35:2:35:3 | star expression | main.go:35:8:35:9 | 19 | +| main.go:35:2:35:3 | star expression | main.go:36:1:36:1 | exit | +| main.go:35:2:35:9 | ... += ... | main.go:35:2:35:3 | assignment to star expression | +| main.go:35:3:35:3 | x | main.go:35:2:35:3 | star expression | +| main.go:35:8:35:9 | 19 | main.go:35:2:35:9 | ... += ... | +| main.go:38:1:38:1 | entry | main.go:39:2:39:2 | skip | +| main.go:38:1:45:1 | function declaration | main.go:47:6:47:8 | skip | +| main.go:38:6:38:8 | skip | main.go:38:1:45:1 | function declaration | +| main.go:39:2:39:2 | assignment to x | main.go:40:2:40:4 | skip | +| main.go:39:2:39:2 | skip | main.go:39:7:39:8 | 23 | +| main.go:39:7:39:8 | 23 | main.go:39:2:39:2 | assignment to x | +| main.go:40:2:40:4 | assignment to ptr | main.go:41:5:41:8 | cond | +| main.go:40:2:40:4 | skip | main.go:40:10:40:10 | x | +| main.go:40:9:40:10 | &... | main.go:40:2:40:4 | assignment to ptr | +| main.go:40:10:40:10 | x | main.go:40:9:40:10 | &... | +| main.go:41:5:41:8 | cond | main.go:41:5:41:10 | call to cond | +| main.go:41:5:41:10 | call to cond | main.go:41:10:41:10 | call to cond is false | +| main.go:41:5:41:10 | call to cond | main.go:41:10:41:10 | call to cond is true | +| main.go:41:5:41:10 | call to cond | main.go:45:1:45:1 | exit | +| main.go:41:10:41:10 | call to cond is false | main.go:44:2:44:10 | selection of Print | +| main.go:41:10:41:10 | call to cond is true | main.go:42:3:42:6 | bump | +| main.go:42:3:42:6 | bump | main.go:42:8:42:10 | ptr | +| main.go:42:3:42:11 | call to bump | main.go:44:2:44:10 | selection of Print | +| main.go:42:3:42:11 | call to bump | main.go:45:1:45:1 | exit | +| main.go:42:8:42:10 | ptr | main.go:42:3:42:11 | call to bump | +| main.go:44:2:44:10 | selection of Print | main.go:44:12:44:12 | x | +| main.go:44:2:44:13 | call to Print | main.go:45:1:45:1 | exit | +| main.go:44:12:44:12 | x | main.go:44:2:44:13 | call to Print | +| main.go:47:1:47:1 | entry | main.go:47:13:47:18 | zero value for result | +| main.go:47:1:50:1 | function declaration | main.go:52:6:52:9 | skip | +| main.go:47:6:47:8 | skip | main.go:47:1:50:1 | function declaration | +| main.go:47:13:47:18 | implicit read of result | main.go:50:1:50:1 | exit | +| main.go:47:13:47:18 | initialization of result | main.go:48:2:48:7 | skip | +| main.go:47:13:47:18 | zero value for result | main.go:47:13:47:18 | initialization of result | +| main.go:48:2:48:7 | assignment to result | main.go:49:2:49:7 | return statement | +| main.go:48:2:48:7 | skip | main.go:48:11:48:12 | 42 | +| main.go:48:11:48:12 | 42 | main.go:48:2:48:7 | assignment to result | +| main.go:49:2:49:7 | return statement | main.go:47:13:47:18 | implicit read of result | +| main.go:52:1:52:1 | entry | main.go:52:14:52:19 | zero value for result | +| main.go:52:1:54:1 | function declaration | main.go:56:6:56:10 | skip | +| main.go:52:6:52:9 | skip | main.go:52:1:54:1 | function declaration | +| main.go:52:14:52:19 | implicit read of result | main.go:54:1:54:1 | exit | +| main.go:52:14:52:19 | initialization of result | main.go:53:2:53:7 | return statement | +| main.go:52:14:52:19 | zero value for result | main.go:52:14:52:19 | initialization of result | +| main.go:53:2:53:7 | return statement | main.go:52:14:52:19 | implicit read of result | +| main.go:56:1:56:1 | entry | main.go:57:6:57:6 | skip | +| main.go:56:1:80:1 | function declaration | main.go:82:6:82:13 | skip | +| main.go:56:6:56:10 | skip | main.go:56:1:80:1 | function declaration | +| main.go:57:6:57:6 | assignment to x | main.go:58:6:58:9 | cond | +| main.go:57:6:57:6 | skip | main.go:57:6:57:6 | zero value for x | +| main.go:57:6:57:6 | zero value for x | main.go:57:6:57:6 | assignment to x | +| main.go:58:6:58:9 | cond | main.go:58:6:58:11 | call to cond | +| main.go:58:6:58:11 | call to cond | main.go:58:11:58:11 | call to cond is false | +| main.go:58:6:58:11 | call to cond | main.go:58:11:58:11 | call to cond is true | +| main.go:58:6:58:11 | call to cond | main.go:80:1:80:1 | exit | +| main.go:58:11:58:11 | call to cond is false | main.go:61:2:61:10 | selection of Print | +| main.go:58:11:58:11 | call to cond is true | main.go:59:3:59:3 | skip | +| main.go:59:3:59:3 | assignment to x | main.go:58:6:58:9 | cond | +| main.go:59:3:59:3 | skip | main.go:59:7:59:7 | 2 | +| main.go:59:7:59:7 | 2 | main.go:59:3:59:3 | assignment to x | +| main.go:61:2:61:10 | selection of Print | main.go:61:12:61:12 | x | +| main.go:61:2:61:13 | call to Print | main.go:63:2:63:2 | skip | +| main.go:61:2:61:13 | call to Print | main.go:80:1:80:1 | exit | +| main.go:61:12:61:12 | x | main.go:61:2:61:13 | call to Print | +| main.go:63:2:63:2 | assignment to y | main.go:64:6:64:6 | skip | +| main.go:63:2:63:2 | skip | main.go:63:7:63:7 | 1 | +| main.go:63:7:63:7 | 1 | main.go:63:2:63:2 | assignment to y | +| main.go:64:6:64:6 | assignment to i | main.go:65:6:65:9 | cond | +| main.go:64:6:64:6 | skip | main.go:64:11:64:11 | 0 | +| main.go:64:11:64:11 | 0 | main.go:64:6:64:6 | assignment to i | +| main.go:64:16:64:16 | i | main.go:64:16:64:18 | 1 | +| main.go:64:16:64:18 | 1 | main.go:64:16:64:18 | rhs of increment statement | +| main.go:64:16:64:18 | increment statement | main.go:65:6:65:9 | cond | +| main.go:64:16:64:18 | rhs of increment statement | main.go:64:16:64:18 | increment statement | +| main.go:65:6:65:9 | cond | main.go:65:6:65:11 | call to cond | +| main.go:65:6:65:11 | call to cond | main.go:65:11:65:11 | call to cond is false | +| main.go:65:6:65:11 | call to cond | main.go:65:11:65:11 | call to cond is true | +| main.go:65:6:65:11 | call to cond | main.go:80:1:80:1 | exit | +| main.go:65:11:65:11 | call to cond is false | main.go:68:3:68:3 | skip | +| main.go:65:11:65:11 | call to cond is true | main.go:66:4:66:8 | skip | +| main.go:66:4:66:8 | skip | main.go:70:2:70:10 | selection of Print | +| main.go:68:3:68:3 | assignment to y | main.go:64:16:64:16 | i | +| main.go:68:3:68:3 | skip | main.go:68:7:68:7 | 2 | +| main.go:68:7:68:7 | 2 | main.go:68:3:68:3 | assignment to y | +| main.go:70:2:70:10 | selection of Print | main.go:70:12:70:12 | y | +| main.go:70:2:70:13 | call to Print | main.go:72:2:72:2 | skip | +| main.go:70:2:70:13 | call to Print | main.go:80:1:80:1 | exit | +| main.go:70:12:70:12 | y | main.go:70:2:70:13 | call to Print | +| main.go:72:2:72:2 | assignment to z | main.go:73:6:73:6 | skip | +| main.go:72:2:72:2 | skip | main.go:72:7:72:7 | 1 | +| main.go:72:7:72:7 | 1 | main.go:72:2:72:2 | assignment to z | +| main.go:73:6:73:6 | assignment to i | main.go:74:3:74:3 | skip | +| main.go:73:6:73:6 | skip | main.go:73:11:73:11 | 0 | +| main.go:73:11:73:11 | 0 | main.go:73:6:73:6 | assignment to i | +| main.go:73:16:73:16 | i | main.go:73:16:73:18 | 1 | +| main.go:73:16:73:18 | 1 | main.go:73:16:73:18 | rhs of increment statement | +| main.go:73:16:73:18 | increment statement | main.go:74:3:74:3 | skip | +| main.go:73:16:73:18 | rhs of increment statement | main.go:73:16:73:18 | increment statement | +| main.go:74:3:74:3 | assignment to z | main.go:75:6:75:9 | cond | +| main.go:74:3:74:3 | skip | main.go:74:7:74:7 | 2 | +| main.go:74:7:74:7 | 2 | main.go:74:3:74:3 | assignment to z | +| main.go:75:6:75:9 | cond | main.go:75:6:75:11 | call to cond | +| main.go:75:6:75:11 | call to cond | main.go:75:11:75:11 | call to cond is false | +| main.go:75:6:75:11 | call to cond | main.go:75:11:75:11 | call to cond is true | +| main.go:75:6:75:11 | call to cond | main.go:80:1:80:1 | exit | +| main.go:75:11:75:11 | call to cond is false | main.go:73:16:73:16 | i | +| main.go:75:11:75:11 | call to cond is true | main.go:76:4:76:8 | skip | +| main.go:76:4:76:8 | skip | main.go:79:2:79:10 | selection of Print | +| main.go:79:2:79:10 | selection of Print | main.go:79:12:79:12 | z | +| main.go:79:2:79:13 | call to Print | main.go:80:1:80:1 | exit | +| main.go:79:12:79:12 | z | main.go:79:2:79:13 | call to Print | +| main.go:82:1:82:1 | entry | main.go:82:18:82:18 | zero value for a | +| main.go:82:1:86:1 | function declaration | main.go:0:0:0:0 | exit | +| main.go:82:6:82:13 | skip | main.go:82:1:86:1 | function declaration | +| main.go:82:18:82:18 | implicit read of a | main.go:82:25:82:25 | implicit read of b | +| main.go:82:18:82:18 | initialization of a | main.go:82:25:82:25 | zero value for b | +| main.go:82:18:82:18 | zero value for a | main.go:82:18:82:18 | initialization of a | +| main.go:82:25:82:25 | implicit read of b | main.go:86:1:86:1 | exit | +| main.go:82:25:82:25 | initialization of b | main.go:83:2:83:2 | skip | +| main.go:82:25:82:25 | zero value for b | main.go:82:25:82:25 | initialization of b | +| main.go:83:2:83:2 | assignment to x | main.go:84:2:84:2 | skip | +| main.go:83:2:83:2 | skip | main.go:83:7:83:8 | 23 | +| main.go:83:7:83:8 | 23 | main.go:83:2:83:2 | assignment to x | +| main.go:84:2:84:2 | assignment to x | main.go:84:5:84:5 | assignment to a | +| main.go:84:2:84:2 | skip | main.go:84:5:84:5 | skip | +| main.go:84:5:84:5 | assignment to a | main.go:85:2:85:7 | return statement | +| main.go:84:5:84:5 | skip | main.go:84:9:84:9 | x | +| main.go:84:9:84:9 | x | main.go:84:11:84:12 | 19 | +| main.go:84:9:84:12 | ...+... | main.go:84:15:84:15 | x | +| main.go:84:11:84:12 | 19 | main.go:84:9:84:12 | ...+... | +| main.go:84:15:84:15 | x | main.go:84:2:84:2 | assignment to x | +| main.go:85:2:85:7 | return statement | main.go:82:18:82:18 | implicit read of a | +| stmts2.go:0:0:0:0 | entry | stmts2.go:3:6:3:11 | skip | +| stmts2.go:3:1:3:1 | entry | stmts2.go:4:2:4:2 | skip | +| stmts2.go:3:1:7:1 | function declaration | stmts2.go:9:6:9:11 | skip | +| stmts2.go:3:6:3:11 | skip | stmts2.go:3:1:7:1 | function declaration | +| stmts2.go:4:2:4:2 | skip | stmts2.go:4:6:4:10 | test7 | +| stmts2.go:4:6:4:10 | test7 | stmts2.go:4:12:4:12 | 0 | +| stmts2.go:4:6:4:13 | call to test7 | stmts2.go:5:6:5:6 | skip | +| stmts2.go:4:6:4:13 | call to test7 | stmts2.go:7:1:7:1 | exit | +| stmts2.go:4:12:4:12 | 0 | stmts2.go:4:6:4:13 | call to test7 | +| stmts2.go:5:6:5:6 | skip | stmts2.go:5:10:5:14 | test7 | +| stmts2.go:5:10:5:14 | test7 | stmts2.go:5:16:5:16 | 1 | +| stmts2.go:5:10:5:17 | call to test7 | stmts2.go:6:9:6:9 | 2 | +| stmts2.go:5:10:5:17 | call to test7 | stmts2.go:7:1:7:1 | exit | +| stmts2.go:5:16:5:16 | 1 | stmts2.go:5:10:5:17 | call to test7 | +| stmts2.go:6:2:6:9 | return statement | stmts2.go:7:1:7:1 | exit | +| stmts2.go:6:9:6:9 | 2 | stmts2.go:6:2:6:9 | return statement | +| stmts2.go:9:1:9:1 | entry | stmts2.go:10:2:10:2 | skip | +| stmts2.go:9:1:13:1 | function declaration | stmts2.go:15:6:15:11 | skip | +| stmts2.go:9:6:9:11 | skip | stmts2.go:9:1:13:1 | function declaration | +| stmts2.go:10:2:10:2 | skip | stmts2.go:10:5:10:5 | skip | +| stmts2.go:10:2:10:14 | ... := ...[0] | stmts2.go:10:2:10:14 | ... := ...[1] | +| stmts2.go:10:2:10:14 | ... := ...[1] | stmts2.go:10:5:10:5 | assignment to x | +| stmts2.go:10:5:10:5 | assignment to x | stmts2.go:11:6:11:6 | skip | +| stmts2.go:10:5:10:5 | skip | stmts2.go:10:10:10:12 | gen | +| stmts2.go:10:10:10:12 | gen | stmts2.go:10:10:10:14 | call to gen | +| stmts2.go:10:10:10:14 | call to gen | stmts2.go:10:2:10:14 | ... := ...[0] | +| stmts2.go:10:10:10:14 | call to gen | stmts2.go:13:1:13:1 | exit | +| stmts2.go:11:6:11:6 | skip | stmts2.go:11:9:11:9 | skip | +| stmts2.go:11:6:11:17 | value declaration specifier[0] | stmts2.go:11:6:11:17 | value declaration specifier[1] | +| stmts2.go:11:6:11:17 | value declaration specifier[1] | stmts2.go:11:9:11:9 | assignment to y | +| stmts2.go:11:9:11:9 | assignment to y | stmts2.go:12:9:12:9 | x | +| stmts2.go:11:9:11:9 | skip | stmts2.go:11:13:11:15 | gen | +| stmts2.go:11:13:11:15 | gen | stmts2.go:11:13:11:17 | call to gen | +| stmts2.go:11:13:11:17 | call to gen | stmts2.go:11:6:11:17 | value declaration specifier[0] | +| stmts2.go:11:13:11:17 | call to gen | stmts2.go:13:1:13:1 | exit | +| stmts2.go:12:2:12:13 | return statement | stmts2.go:13:1:13:1 | exit | +| stmts2.go:12:9:12:9 | x | stmts2.go:12:13:12:13 | y | +| stmts2.go:12:9:12:13 | ...+... | stmts2.go:12:2:12:13 | return statement | +| stmts2.go:12:13:12:13 | y | stmts2.go:12:9:12:13 | ...+... | +| stmts2.go:15:1:15:1 | entry | stmts2.go:15:13:15:14 | argument corresponding to ch | +| stmts2.go:15:1:28:1 | function declaration | stmts2.go:0:0:0:0 | exit | +| stmts2.go:15:6:15:11 | skip | stmts2.go:15:1:28:1 | function declaration | +| stmts2.go:15:13:15:14 | argument corresponding to ch | stmts2.go:15:13:15:14 | initialization of ch | +| stmts2.go:15:13:15:14 | initialization of ch | stmts2.go:17:13:17:14 | ch | +| stmts2.go:16:2:26:2 | select statement | stmts2.go:17:11:17:14 | <-... | +| stmts2.go:16:2:26:2 | select statement | stmts2.go:18:15:18:18 | <-... | +| stmts2.go:16:2:26:2 | select statement | stmts2.go:20:15:20:18 | <-... | +| stmts2.go:16:2:26:2 | select statement | stmts2.go:25:14:25:17 | <-... | +| stmts2.go:17:2:17:15 | skip | stmts2.go:27:9:27:9 | 1 | +| stmts2.go:17:7:17:7 | skip | stmts2.go:17:2:17:15 | skip | +| stmts2.go:17:11:17:14 | <-... | stmts2.go:17:7:17:7 | skip | +| stmts2.go:17:13:17:14 | ch | stmts2.go:18:17:18:18 | ch | +| stmts2.go:18:7:18:7 | assignment to x | stmts2.go:18:7:18:18 | ... := ...[1] | +| stmts2.go:18:7:18:7 | skip | stmts2.go:18:10:18:10 | skip | +| stmts2.go:18:7:18:18 | ... := ...[0] | stmts2.go:18:7:18:7 | assignment to x | +| stmts2.go:18:7:18:18 | ... := ...[1] | stmts2.go:19:10:19:10 | x | +| stmts2.go:18:10:18:10 | skip | stmts2.go:18:7:18:18 | ... := ...[0] | +| stmts2.go:18:15:18:18 | <-... | stmts2.go:18:7:18:7 | skip | +| stmts2.go:18:17:18:18 | ch | stmts2.go:20:17:20:18 | ch | +| stmts2.go:19:3:19:10 | return statement | stmts2.go:28:1:28:1 | exit | +| stmts2.go:19:10:19:10 | x | stmts2.go:19:3:19:10 | return statement | +| stmts2.go:20:7:20:7 | skip | stmts2.go:20:10:20:10 | skip | +| stmts2.go:20:7:20:18 | ... := ...[0] | stmts2.go:20:7:20:18 | ... := ...[1] | +| stmts2.go:20:7:20:18 | ... := ...[1] | stmts2.go:20:10:20:10 | assignment to y | +| stmts2.go:20:10:20:10 | assignment to y | stmts2.go:21:6:21:6 | y | +| stmts2.go:20:10:20:10 | skip | stmts2.go:20:7:20:18 | ... := ...[0] | +| stmts2.go:20:15:20:18 | <-... | stmts2.go:20:7:20:7 | skip | +| stmts2.go:20:17:20:18 | ch | stmts2.go:25:16:25:17 | ch | +| stmts2.go:21:6:21:6 | y | stmts2.go:21:6:21:6 | y is false | +| stmts2.go:21:6:21:6 | y | stmts2.go:21:6:21:6 | y is true | +| stmts2.go:21:6:21:6 | y is false | stmts2.go:24:10:24:10 | 0 | +| stmts2.go:21:6:21:6 | y is true | stmts2.go:22:4:22:8 | skip | +| stmts2.go:22:4:22:8 | skip | stmts2.go:27:9:27:9 | 1 | +| stmts2.go:24:3:24:10 | return statement | stmts2.go:28:1:28:1 | exit | +| stmts2.go:24:10:24:10 | 0 | stmts2.go:24:3:24:10 | return statement | +| stmts2.go:25:2:25:18 | skip | stmts2.go:27:9:27:9 | 1 | +| stmts2.go:25:7:25:7 | skip | stmts2.go:25:10:25:10 | skip | +| stmts2.go:25:7:25:17 | ... = ...[0] | stmts2.go:25:7:25:17 | ... = ...[1] | +| stmts2.go:25:7:25:17 | ... = ...[1] | stmts2.go:25:2:25:18 | skip | +| stmts2.go:25:10:25:10 | skip | stmts2.go:25:7:25:17 | ... = ...[0] | +| stmts2.go:25:14:25:17 | <-... | stmts2.go:25:7:25:7 | skip | +| stmts2.go:25:16:25:17 | ch | stmts2.go:16:2:26:2 | select statement | +| stmts2.go:27:2:27:9 | return statement | stmts2.go:28:1:28:1 | exit | +| stmts2.go:27:9:27:9 | 1 | stmts2.go:27:2:27:9 | return statement | +| stmts3.go:0:0:0:0 | entry | stmts3.go:3:1:3:13 | skip | +| stmts3.go:3:1:3:13 | skip | stmts3.go:5:6:5:11 | skip | +| stmts3.go:5:1:5:1 | entry | stmts3.go:7:3:7:5 | skip | +| stmts3.go:5:1:12:1 | function declaration | stmts3.go:14:6:14:11 | skip | +| stmts3.go:5:6:5:11 | skip | stmts3.go:5:1:12:1 | function declaration | +| stmts3.go:7:3:7:5 | assignment to red | stmts3.go:8:3:8:7 | skip | +| stmts3.go:7:3:7:5 | skip | stmts3.go:7:9:7:12 | iota | +| stmts3.go:7:9:7:12 | iota | stmts3.go:7:3:7:5 | assignment to red | +| stmts3.go:8:3:8:7 | assignment to green | stmts3.go:9:3:9:6 | skip | +| stmts3.go:8:3:8:7 | skip | stmts3.go:8:3:8:7 | zero value for green | +| stmts3.go:8:3:8:7 | zero value for green | stmts3.go:8:3:8:7 | assignment to green | +| stmts3.go:9:3:9:6 | assignment to blue | stmts3.go:11:9:11:26 | ...-... | +| stmts3.go:9:3:9:6 | skip | stmts3.go:9:3:9:6 | zero value for blue | +| stmts3.go:9:3:9:6 | zero value for blue | stmts3.go:9:3:9:6 | assignment to blue | +| stmts3.go:11:2:11:26 | return statement | stmts3.go:12:1:12:1 | exit | +| stmts3.go:11:9:11:26 | ...-... | stmts3.go:11:2:11:26 | return statement | +| stmts3.go:14:1:14:1 | entry | stmts3.go:14:13:14:13 | argument corresponding to x | +| stmts3.go:14:1:16:1 | function declaration | stmts3.go:18:6:18:11 | skip | +| stmts3.go:14:6:14:11 | skip | stmts3.go:14:1:16:1 | function declaration | +| stmts3.go:14:13:14:13 | argument corresponding to x | stmts3.go:14:13:14:13 | initialization of x | +| stmts3.go:14:13:14:13 | initialization of x | stmts3.go:15:3:15:3 | x | +| stmts3.go:15:2:15:3 | assignment to star expression | stmts3.go:16:1:16:1 | exit | +| stmts3.go:15:2:15:3 | skip | stmts3.go:15:7:15:8 | 42 | +| stmts3.go:15:2:15:3 | skip | stmts3.go:16:1:16:1 | exit | +| stmts3.go:15:3:15:3 | x | stmts3.go:15:2:15:3 | skip | +| stmts3.go:15:7:15:8 | 42 | stmts3.go:15:2:15:3 | assignment to star expression | +| stmts3.go:18:1:18:1 | entry | stmts3.go:19:2:19:11 | skip | +| stmts3.go:18:1:20:1 | function declaration | stmts3.go:0:0:0:0 | exit | +| stmts3.go:18:6:18:11 | skip | stmts3.go:18:1:20:1 | function declaration | +| stmts3.go:19:2:19:11 | assignment to Usage | stmts3.go:20:1:20:1 | exit | +| stmts3.go:19:2:19:11 | skip | stmts3.go:19:15:19:23 | function literal | +| stmts3.go:19:15:19:15 | entry | stmts3.go:19:22:19:23 | skip | +| stmts3.go:19:15:19:23 | function literal | stmts3.go:19:2:19:11 | assignment to Usage | +| stmts3.go:19:22:19:23 | skip | stmts3.go:19:23:19:23 | exit | +| stmts4.go:0:0:0:0 | entry | stmts4.go:3:5:3:5 | skip | +| stmts4.go:3:5:3:5 | skip | stmts4.go:3:5:3:5 | zero value for _ | +| stmts4.go:3:5:3:5 | skip | stmts4.go:5:6:5:11 | skip | +| stmts4.go:5:1:5:26 | function declaration | stmts4.go:0:0:0:0 | exit | +| stmts4.go:5:6:5:11 | skip | stmts4.go:5:1:5:26 | function declaration | +| stmts5.go:0:0:0:0 | entry | stmts5.go:3:1:5:1 | skip | +| stmts5.go:3:1:5:1 | skip | stmts5.go:7:17:7:20 | skip | +| stmts5.go:7:1:7:1 | entry | stmts5.go:7:7:7:8 | argument corresponding to me | +| stmts5.go:7:1:9:1 | function declaration | stmts5.go:11:14:11:16 | skip | +| stmts5.go:7:7:7:8 | argument corresponding to me | stmts5.go:7:7:7:8 | initialization of me | +| stmts5.go:7:7:7:8 | initialization of me | stmts5.go:7:22:7:26 | argument corresponding to other | +| stmts5.go:7:17:7:20 | skip | stmts5.go:7:1:9:1 | function declaration | +| stmts5.go:7:22:7:26 | argument corresponding to other | stmts5.go:7:22:7:26 | initialization of other | +| stmts5.go:7:22:7:26 | initialization of other | stmts5.go:8:2:8:3 | me | +| stmts5.go:8:2:8:3 | me | stmts5.go:8:2:8:7 | selection of val | +| stmts5.go:8:2:8:7 | assignment to field val | stmts5.go:9:1:9:1 | exit | +| stmts5.go:8:2:8:7 | selection of val | stmts5.go:8:12:8:16 | other | +| stmts5.go:8:2:8:7 | selection of val | stmts5.go:9:1:9:1 | exit | +| stmts5.go:8:2:8:16 | ... += ... | stmts5.go:8:2:8:7 | assignment to field val | +| stmts5.go:8:12:8:16 | other | stmts5.go:8:2:8:16 | ... += ... | +| stmts5.go:11:1:11:1 | entry | stmts5.go:11:20:12:1 | skip | +| stmts5.go:11:1:12:1 | function declaration | stmts5.go:0:0:0:0 | exit | +| stmts5.go:11:14:11:16 | skip | stmts5.go:11:1:12:1 | function declaration | +| stmts5.go:11:20:12:1 | skip | stmts5.go:12:1:12:1 | exit | +| stmts6.go:0:0:0:0 | entry | stmts6.go:3:6:3:11 | skip | +| stmts6.go:3:1:3:1 | entry | stmts6.go:4:5:4:8 | true | +| stmts6.go:3:1:8:1 | function declaration | stmts6.go:0:0:0:0 | exit | +| stmts6.go:3:6:3:11 | skip | stmts6.go:3:1:8:1 | function declaration | +| stmts6.go:4:5:4:8 | true | stmts6.go:4:8:4:8 | true is true | +| stmts6.go:4:8:4:8 | true is false | stmts6.go:7:9:7:10 | 23 | +| stmts6.go:4:8:4:8 | true is true | stmts6.go:5:10:5:11 | 42 | +| stmts6.go:5:3:5:11 | return statement | stmts6.go:8:1:8:1 | exit | +| stmts6.go:5:10:5:11 | 42 | stmts6.go:5:3:5:11 | return statement | +| stmts6.go:7:9:7:10 | 23 | stmts6.go:7:2:7:10 | return statement | +| stmts7.go:0:0:0:0 | entry | stmts7.go:3:1:3:12 | skip | +| stmts7.go:3:1:3:12 | skip | stmts7.go:5:6:5:17 | skip | +| stmts7.go:5:1:5:1 | entry | stmts7.go:6:2:6:5 | skip | +| stmts7.go:5:1:8:1 | function declaration | stmts7.go:10:6:10:15 | skip | +| stmts7.go:5:6:5:17 | skip | stmts7.go:5:1:8:1 | function declaration | +| stmts7.go:6:2:6:5 | assignment to blah | stmts7.go:7:2:7:12 | selection of Println | +| stmts7.go:6:2:6:5 | skip | stmts7.go:6:10:6:16 | recover | +| stmts7.go:6:10:6:16 | recover | stmts7.go:6:10:6:18 | call to recover | +| stmts7.go:6:10:6:18 | call to recover | stmts7.go:6:2:6:5 | assignment to blah | +| stmts7.go:7:2:7:12 | selection of Println | stmts7.go:7:14:7:26 | "recovered: " | +| stmts7.go:7:2:7:33 | call to Println | stmts7.go:8:1:8:1 | exit | +| stmts7.go:7:14:7:26 | "recovered: " | stmts7.go:7:29:7:32 | blah | +| stmts7.go:7:29:7:32 | blah | stmts7.go:7:2:7:33 | call to Println | +| stmts7.go:10:1:10:1 | entry | stmts7.go:11:8:11:19 | recoverPanic | +| stmts7.go:10:1:13:1 | function declaration | stmts7.go:15:1:17:1 | skip | +| stmts7.go:10:6:10:15 | skip | stmts7.go:10:1:13:1 | function declaration | +| stmts7.go:11:2:11:21 | defer statement | stmts7.go:12:2:12:6 | panic | +| stmts7.go:11:8:11:19 | recoverPanic | stmts7.go:11:2:11:21 | defer statement | +| stmts7.go:11:8:11:21 | call to recoverPanic | stmts7.go:13:1:13:1 | exit | +| stmts7.go:12:2:12:6 | panic | stmts7.go:12:8:12:9 | "" | +| stmts7.go:12:2:12:10 | call to panic | stmts7.go:11:8:11:21 | call to recoverPanic | +| stmts7.go:12:8:12:9 | "" | stmts7.go:12:2:12:10 | call to panic | +| stmts7.go:15:1:17:1 | skip | stmts7.go:19:26:19:28 | skip | +| stmts7.go:19:1:19:1 | entry | stmts7.go:19:7:19:13 | argument corresponding to methods | +| stmts7.go:19:1:21:1 | function declaration | stmts7.go:23:6:23:14 | skip | +| stmts7.go:19:7:19:13 | argument corresponding to methods | stmts7.go:19:7:19:13 | initialization of methods | +| stmts7.go:19:7:19:13 | initialization of methods | stmts7.go:20:2:20:8 | methods | +| stmts7.go:19:26:19:28 | skip | stmts7.go:19:1:21:1 | function declaration | +| stmts7.go:20:2:20:8 | methods | stmts7.go:20:2:20:11 | selection of fn | +| stmts7.go:20:2:20:11 | selection of fn | stmts7.go:20:2:20:13 | call to fn | +| stmts7.go:20:2:20:11 | selection of fn | stmts7.go:21:1:21:1 | exit | +| stmts7.go:20:2:20:13 | call to fn | stmts7.go:21:1:21:1 | exit | +| stmts7.go:23:1:23:1 | entry | stmts7.go:23:16:23:23 | argument corresponding to callback | +| stmts7.go:23:1:28:1 | function declaration | stmts7.go:0:0:0:0 | exit | +| stmts7.go:23:6:23:14 | skip | stmts7.go:23:1:28:1 | function declaration | +| stmts7.go:23:16:23:23 | argument corresponding to callback | stmts7.go:23:16:23:23 | initialization of callback | +| stmts7.go:23:16:23:23 | initialization of callback | stmts7.go:24:8:24:15 | callback | +| stmts7.go:24:2:24:20 | defer statement | stmts7.go:25:10:25:17 | callback | +| stmts7.go:24:8:24:15 | callback | stmts7.go:24:8:24:18 | selection of fn | +| stmts7.go:24:8:24:18 | selection of fn | stmts7.go:24:2:24:20 | defer statement | +| stmts7.go:24:8:24:18 | selection of fn | stmts7.go:28:1:28:1 | exit | +| stmts7.go:24:8:24:20 | call to fn | stmts7.go:28:1:28:1 | exit | +| stmts7.go:25:2:25:23 | defer statement | stmts7.go:26:2:26:12 | selection of Println | +| stmts7.go:25:8:25:21 | selection of fn | stmts7.go:24:8:24:20 | call to fn | +| stmts7.go:25:8:25:21 | selection of fn | stmts7.go:25:2:25:23 | defer statement | +| stmts7.go:25:8:25:23 | call to fn | stmts7.go:24:8:24:20 | call to fn | +| stmts7.go:25:9:25:17 | &... | stmts7.go:25:8:25:21 | selection of fn | +| stmts7.go:25:10:25:17 | callback | stmts7.go:25:9:25:17 | &... | +| stmts7.go:26:2:26:12 | selection of Println | stmts7.go:26:14:26:30 | "print something" | +| stmts7.go:26:2:26:31 | call to Println | stmts7.go:25:8:25:23 | call to fn | +| stmts7.go:26:2:26:31 | call to Println | stmts7.go:27:9:27:13 | false | +| stmts7.go:26:14:26:30 | "print something" | stmts7.go:26:2:26:31 | call to Println | +| stmts7.go:27:2:27:13 | return statement | stmts7.go:25:8:25:23 | call to fn | +| stmts7.go:27:9:27:13 | false | stmts7.go:27:2:27:13 | return statement | +| stmts8.go:0:0:0:0 | entry | stmts8.go:3:6:3:11 | skip | +| stmts8.go:3:1:3:1 | entry | stmts8.go:3:13:3:13 | argument corresponding to x | +| stmts8.go:3:1:7:1 | function declaration | stmts8.go:9:6:9:12 | skip | +| stmts8.go:3:6:3:11 | skip | stmts8.go:3:1:7:1 | function declaration | +| stmts8.go:3:13:3:13 | argument corresponding to x | stmts8.go:3:13:3:13 | initialization of x | +| stmts8.go:3:13:3:13 | initialization of x | stmts8.go:4:2:4:2 | skip | +| stmts8.go:4:2:4:2 | assignment to y | stmts8.go:5:2:5:2 | skip | +| stmts8.go:4:2:4:2 | skip | stmts8.go:4:7:4:7 | x | +| stmts8.go:4:7:4:7 | x | stmts8.go:4:12:4:12 | 5 | +| stmts8.go:4:7:4:12 | ...>>... | stmts8.go:4:2:4:2 | assignment to y | +| stmts8.go:4:12:4:12 | 5 | stmts8.go:4:7:4:12 | ...>>... | +| stmts8.go:5:2:5:2 | assignment to z | stmts8.go:6:9:6:9 | z | +| stmts8.go:5:2:5:2 | skip | stmts8.go:5:7:5:7 | x | +| stmts8.go:5:7:5:7 | x | stmts8.go:5:12:5:12 | 1 | +| stmts8.go:5:7:5:13 | ...%... | stmts8.go:5:2:5:2 | assignment to z | +| stmts8.go:5:12:5:12 | 1 | stmts8.go:5:7:5:13 | ...%... | +| stmts8.go:6:2:6:17 | return statement | stmts8.go:7:1:7:1 | exit | +| stmts8.go:6:9:6:9 | z | stmts8.go:6:12:6:12 | y | +| stmts8.go:6:12:6:12 | y | stmts8.go:6:16:6:17 | 13 | +| stmts8.go:6:12:6:17 | ...%... | stmts8.go:6:2:6:17 | return statement | +| stmts8.go:6:16:6:17 | 13 | stmts8.go:6:12:6:17 | ...%... | +| stmts8.go:9:1:9:1 | entry | stmts8.go:10:5:10:9 | linux | +| stmts8.go:9:1:14:1 | function declaration | stmts8.go:0:0:0:0 | exit | +| stmts8.go:9:6:9:12 | skip | stmts8.go:9:1:14:1 | function declaration | +| stmts8.go:10:5:10:9 | linux | stmts8.go:10:9:10:9 | linux is false | +| stmts8.go:10:5:10:9 | linux | stmts8.go:10:9:10:9 | linux is true | +| stmts8.go:10:9:10:9 | linux is false | stmts8.go:13:9:13:13 | false | +| stmts8.go:10:9:10:9 | linux is true | stmts8.go:11:10:11:13 | true | +| stmts8.go:11:3:11:13 | return statement | stmts8.go:14:1:14:1 | exit | +| stmts8.go:11:10:11:13 | true | stmts8.go:11:3:11:13 | return statement | +| stmts8.go:13:2:13:13 | return statement | stmts8.go:14:1:14:1 | exit | +| stmts8.go:13:9:13:13 | false | stmts8.go:13:2:13:13 | return statement | +| stmts.go:0:0:0:0 | entry | stmts.go:3:1:3:12 | skip | +| stmts.go:3:1:3:12 | skip | stmts.go:8:6:8:10 | skip | +| stmts.go:8:1:8:1 | entry | stmts.go:8:12:8:12 | argument corresponding to b | +| stmts.go:8:1:41:1 | function declaration | stmts.go:44:6:44:10 | skip | +| stmts.go:8:6:8:10 | skip | stmts.go:8:1:41:1 | function declaration | +| stmts.go:8:12:8:12 | argument corresponding to b | stmts.go:8:12:8:12 | initialization of b | +| stmts.go:8:12:8:12 | initialization of b | stmts.go:10:7:10:7 | b | +| stmts.go:10:6:10:7 | !... | stmts.go:10:7:10:7 | !... is false | +| stmts.go:10:6:10:7 | !... | stmts.go:10:7:10:7 | !... is true | +| stmts.go:10:7:10:7 | !... is false | stmts.go:13:3:14:3 | skip | +| stmts.go:10:7:10:7 | !... is true | stmts.go:11:4:11:13 | skip | +| stmts.go:10:7:10:7 | b | stmts.go:10:6:10:7 | !... | +| stmts.go:11:4:11:13 | skip | stmts.go:21:6:21:9 | true | +| stmts.go:13:3:14:3 | skip | stmts.go:15:3:15:3 | skip | +| stmts.go:15:3:15:3 | skip | stmts.go:18:2:18:12 | selection of Println | +| stmts.go:18:2:18:12 | selection of Println | stmts.go:18:14:18:17 | "Hi" | +| stmts.go:18:2:18:18 | call to Println | stmts.go:21:6:21:9 | true | +| stmts.go:18:2:18:18 | call to Println | stmts.go:41:1:41:1 | exit | +| stmts.go:18:14:18:17 | "Hi" | stmts.go:18:2:18:18 | call to Println | +| stmts.go:21:6:21:9 | true | stmts.go:21:9:21:9 | true is true | +| stmts.go:21:9:21:9 | true is false | stmts.go:37:2:37:2 | skip | +| stmts.go:21:9:21:9 | true is true | stmts.go:22:7:22:7 | skip | +| stmts.go:22:7:22:7 | assignment to i | stmts.go:22:15:22:15 | i | +| stmts.go:22:7:22:7 | skip | stmts.go:22:12:22:12 | 0 | +| stmts.go:22:12:22:12 | 0 | stmts.go:22:7:22:7 | assignment to i | +| stmts.go:22:15:22:15 | i | stmts.go:22:19:22:20 | 10 | +| stmts.go:22:15:22:20 | ...<... | stmts.go:22:20:22:20 | ...<... is false | +| stmts.go:22:15:22:20 | ...<... | stmts.go:22:20:22:20 | ...<... is true | +| stmts.go:22:19:22:20 | 10 | stmts.go:22:15:22:20 | ...<... | +| stmts.go:22:20:22:20 | ...<... is false | stmts.go:21:6:21:9 | true | +| stmts.go:22:20:22:20 | ...<... is true | stmts.go:23:7:23:7 | skip | +| stmts.go:22:23:22:23 | i | stmts.go:22:23:22:25 | 1 | +| stmts.go:22:23:22:25 | 1 | stmts.go:22:23:22:25 | rhs of increment statement | +| stmts.go:22:23:22:25 | increment statement | stmts.go:22:15:22:15 | i | +| stmts.go:22:23:22:25 | rhs of increment statement | stmts.go:22:23:22:25 | increment statement | +| stmts.go:23:7:23:7 | assignment to j | stmts.go:23:19:23:19 | j | +| stmts.go:23:7:23:7 | skip | stmts.go:23:12:23:12 | i | +| stmts.go:23:12:23:12 | i | stmts.go:23:16:23:16 | 1 | +| stmts.go:23:12:23:16 | ...-... | stmts.go:23:7:23:7 | assignment to j | +| stmts.go:23:16:23:16 | 1 | stmts.go:23:12:23:16 | ...-... | +| stmts.go:23:19:23:19 | j | stmts.go:23:23:23:23 | 5 | +| stmts.go:23:19:23:23 | ...>... | stmts.go:23:23:23:23 | ...>... is false | +| stmts.go:23:19:23:23 | ...>... | stmts.go:23:23:23:23 | ...>... is true | +| stmts.go:23:23:23:23 | 5 | stmts.go:23:19:23:23 | ...>... | +| stmts.go:23:23:23:23 | ...>... is false | stmts.go:25:14:25:14 | i | +| stmts.go:23:23:23:23 | ...>... is true | stmts.go:24:5:24:15 | skip | +| stmts.go:24:5:24:15 | skip | stmts.go:37:2:37:2 | skip | +| stmts.go:25:14:25:14 | i | stmts.go:25:18:25:18 | 3 | +| stmts.go:25:14:25:18 | ...<... | stmts.go:25:18:25:18 | ...<... is false | +| stmts.go:25:14:25:18 | ...<... | stmts.go:25:18:25:18 | ...<... is true | +| stmts.go:25:18:25:18 | 3 | stmts.go:25:14:25:18 | ...<... | +| stmts.go:25:18:25:18 | ...<... is false | stmts.go:27:14:27:14 | i | +| stmts.go:25:18:25:18 | ...<... is true | stmts.go:26:5:26:9 | skip | +| stmts.go:26:5:26:9 | skip | stmts.go:21:6:21:9 | true | +| stmts.go:27:14:27:14 | i | stmts.go:27:19:27:19 | 9 | +| stmts.go:27:14:27:19 | ...!=... | stmts.go:27:19:27:19 | ...!=... is false | +| stmts.go:27:14:27:19 | ...!=... | stmts.go:27:19:27:19 | ...!=... is true | +| stmts.go:27:14:27:19 | ...!=... | stmts.go:41:1:41:1 | exit | +| stmts.go:27:19:27:19 | 9 | stmts.go:27:14:27:19 | ...!=... | +| stmts.go:27:19:27:19 | ...!=... is false | stmts.go:29:14:29:14 | i | +| stmts.go:27:19:27:19 | ...!=... is true | stmts.go:28:5:28:18 | skip | +| stmts.go:28:5:28:18 | skip | stmts.go:21:6:21:9 | true | +| stmts.go:29:14:29:14 | i | stmts.go:29:19:29:19 | 4 | +| stmts.go:29:14:29:19 | ...>=... | stmts.go:29:19:29:19 | ...>=... is false | +| stmts.go:29:14:29:19 | ...>=... | stmts.go:29:19:29:19 | ...>=... is true | +| stmts.go:29:19:29:19 | 4 | stmts.go:29:14:29:19 | ...>=... | +| stmts.go:29:19:29:19 | ...>=... is false | stmts.go:32:5:32:12 | skip | +| stmts.go:29:19:29:19 | ...>=... is true | stmts.go:30:5:30:14 | skip | +| stmts.go:30:5:30:14 | skip | stmts.go:21:6:21:9 | true | +| stmts.go:32:5:32:12 | skip | stmts.go:22:23:22:23 | i | +| stmts.go:37:2:37:2 | assignment to k | stmts.go:39:3:39:12 | skip | +| stmts.go:37:2:37:2 | skip | stmts.go:37:7:37:7 | 9 | +| stmts.go:37:7:37:7 | 9 | stmts.go:37:2:37:2 | assignment to k | +| stmts.go:38:10:38:10 | k | stmts.go:38:10:38:12 | 1 | +| stmts.go:38:10:38:12 | 1 | stmts.go:38:10:38:12 | rhs of increment statement | +| stmts.go:38:10:38:12 | increment statement | stmts.go:39:3:39:12 | skip | +| stmts.go:38:10:38:12 | rhs of increment statement | stmts.go:38:10:38:12 | increment statement | +| stmts.go:39:3:39:12 | skip | stmts.go:21:6:21:9 | true | +| stmts.go:44:1:44:1 | entry | stmts.go:44:12:44:14 | argument corresponding to ch1 | +| stmts.go:44:1:60:1 | function declaration | stmts.go:63:6:63:10 | skip | +| stmts.go:44:6:44:10 | skip | stmts.go:44:1:60:1 | function declaration | +| stmts.go:44:12:44:14 | argument corresponding to ch1 | stmts.go:44:12:44:14 | initialization of ch1 | +| stmts.go:44:12:44:14 | initialization of ch1 | stmts.go:44:26:44:28 | argument corresponding to ch2 | +| stmts.go:44:26:44:28 | argument corresponding to ch2 | stmts.go:44:26:44:28 | initialization of ch2 | +| stmts.go:44:26:44:28 | initialization of ch2 | stmts.go:45:6:45:6 | skip | +| stmts.go:45:6:45:6 | assignment to a | stmts.go:46:6:46:6 | skip | +| stmts.go:45:6:45:6 | skip | stmts.go:45:6:45:6 | zero value for a | +| stmts.go:45:6:45:6 | zero value for a | stmts.go:45:6:45:6 | assignment to a | +| stmts.go:46:6:46:6 | assignment to w | stmts.go:49:9:49:11 | ch1 | +| stmts.go:46:6:46:6 | skip | stmts.go:46:6:46:6 | zero value for w | +| stmts.go:46:6:46:6 | zero value for w | stmts.go:46:6:46:6 | assignment to w | +| stmts.go:48:2:57:2 | select statement | stmts.go:49:7:49:11 | <-... | +| stmts.go:48:2:57:2 | select statement | stmts.go:51:17:51:21 | <-... | +| stmts.go:48:2:57:2 | select statement | stmts.go:55:3:55:13 | selection of Println | +| stmts.go:48:2:57:2 | select statement | stmts.go:56:7:56:15 | send statement | +| stmts.go:49:7:49:11 | <-... | stmts.go:50:3:50:13 | selection of Println | +| stmts.go:49:9:49:11 | ch1 | stmts.go:51:19:51:21 | ch2 | +| stmts.go:50:3:50:13 | selection of Println | stmts.go:50:15:50:30 | "Heard from ch1" | +| stmts.go:50:3:50:31 | call to Println | stmts.go:59:2:59:10 | select statement | +| stmts.go:50:3:50:31 | call to Println | stmts.go:60:1:60:1 | exit | +| stmts.go:50:15:50:30 | "Heard from ch1" | stmts.go:50:3:50:31 | call to Println | +| stmts.go:51:7:51:7 | a | stmts.go:51:9:51:9 | 0 | +| stmts.go:51:7:51:10 | assignment to element | stmts.go:51:7:51:21 | ... = ...[1] | +| stmts.go:51:7:51:10 | skip | stmts.go:51:13:51:13 | skip | +| stmts.go:51:7:51:21 | ... = ...[0] | stmts.go:51:7:51:10 | assignment to element | +| stmts.go:51:7:51:21 | ... = ...[1] | stmts.go:51:13:51:13 | assignment to w | +| stmts.go:51:9:51:9 | 0 | stmts.go:51:7:51:10 | skip | +| stmts.go:51:13:51:13 | assignment to w | stmts.go:52:3:52:13 | selection of Println | +| stmts.go:51:13:51:13 | skip | stmts.go:51:7:51:21 | ... = ...[0] | +| stmts.go:51:17:51:21 | <-... | stmts.go:51:7:51:7 | a | +| stmts.go:51:19:51:21 | ch2 | stmts.go:56:7:56:9 | ch1 | +| stmts.go:52:3:52:13 | selection of Println | stmts.go:52:15:52:15 | a | +| stmts.go:52:3:52:16 | call to Println | stmts.go:53:3:53:13 | selection of Println | +| stmts.go:52:3:52:16 | call to Println | stmts.go:60:1:60:1 | exit | +| stmts.go:52:15:52:15 | a | stmts.go:52:3:52:16 | call to Println | +| stmts.go:53:3:53:13 | selection of Println | stmts.go:53:15:53:15 | w | +| stmts.go:53:3:53:16 | call to Println | stmts.go:59:2:59:10 | select statement | +| stmts.go:53:3:53:16 | call to Println | stmts.go:60:1:60:1 | exit | +| stmts.go:53:15:53:15 | w | stmts.go:53:3:53:16 | call to Println | +| stmts.go:55:3:55:13 | selection of Println | stmts.go:55:3:55:15 | call to Println | +| stmts.go:55:3:55:15 | call to Println | stmts.go:59:2:59:10 | select statement | +| stmts.go:55:3:55:15 | call to Println | stmts.go:60:1:60:1 | exit | +| stmts.go:56:2:56:16 | skip | stmts.go:59:2:59:10 | select statement | +| stmts.go:56:7:56:9 | ch1 | stmts.go:56:14:56:15 | 42 | +| stmts.go:56:7:56:15 | send statement | stmts.go:56:2:56:16 | skip | +| stmts.go:56:7:56:15 | send statement | stmts.go:60:1:60:1 | exit | +| stmts.go:56:14:56:15 | 42 | stmts.go:48:2:57:2 | select statement | +| stmts.go:63:1:63:1 | entry | stmts.go:63:12:63:12 | argument corresponding to x | +| stmts.go:63:1:70:1 | function declaration | stmts.go:73:6:73:10 | skip | +| stmts.go:63:6:63:10 | skip | stmts.go:63:1:70:1 | function declaration | +| stmts.go:63:12:63:12 | argument corresponding to x | stmts.go:63:12:63:12 | initialization of x | +| stmts.go:63:12:63:12 | initialization of x | stmts.go:64:5:64:5 | x | +| stmts.go:64:5:64:5 | x | stmts.go:64:9:64:9 | 0 | +| stmts.go:64:5:64:9 | ...>... | stmts.go:64:9:64:9 | ...>... is false | +| stmts.go:64:5:64:9 | ...>... | stmts.go:64:9:64:9 | ...>... is true | +| stmts.go:64:9:64:9 | 0 | stmts.go:64:5:64:9 | ...>... | +| stmts.go:64:9:64:9 | ...>... is false | stmts.go:67:9:67:34 | function literal | +| stmts.go:64:9:64:9 | ...>... is true | stmts.go:65:9:65:33 | function literal | +| stmts.go:65:3:65:35 | defer statement | stmts.go:69:9:69:10 | 42 | +| stmts.go:65:9:65:9 | entry | stmts.go:65:18:65:28 | selection of Println | +| stmts.go:65:9:65:33 | function literal | stmts.go:65:3:65:35 | defer statement | +| stmts.go:65:9:65:35 | function call | stmts.go:70:1:70:1 | exit | +| stmts.go:65:18:65:28 | selection of Println | stmts.go:65:30:65:30 | x | +| stmts.go:65:18:65:31 | call to Println | stmts.go:65:33:65:33 | exit | +| stmts.go:65:30:65:30 | x | stmts.go:65:18:65:31 | call to Println | +| stmts.go:67:3:67:36 | defer statement | stmts.go:69:9:69:10 | 42 | +| stmts.go:67:9:67:9 | entry | stmts.go:67:18:67:28 | selection of Println | +| stmts.go:67:9:67:34 | function literal | stmts.go:67:3:67:36 | defer statement | +| stmts.go:67:9:67:36 | function call | stmts.go:70:1:70:1 | exit | +| stmts.go:67:18:67:28 | selection of Println | stmts.go:67:31:67:31 | x | +| stmts.go:67:18:67:32 | call to Println | stmts.go:67:34:67:34 | exit | +| stmts.go:67:30:67:31 | -... | stmts.go:67:18:67:32 | call to Println | +| stmts.go:67:31:67:31 | x | stmts.go:67:30:67:31 | -... | +| stmts.go:69:2:69:10 | return statement | stmts.go:65:9:65:35 | function call | +| stmts.go:69:2:69:10 | return statement | stmts.go:67:9:67:36 | function call | +| stmts.go:69:9:69:10 | 42 | stmts.go:69:2:69:10 | return statement | +| stmts.go:73:1:73:1 | entry | stmts.go:73:12:73:12 | argument corresponding to x | +| stmts.go:73:1:107:1 | function declaration | stmts.go:110:6:110:10 | skip | +| stmts.go:73:6:73:10 | skip | stmts.go:73:1:107:1 | function declaration | +| stmts.go:73:12:73:12 | argument corresponding to x | stmts.go:73:12:73:12 | initialization of x | +| stmts.go:73:12:73:12 | initialization of x | stmts.go:74:9:74:9 | x | +| stmts.go:74:9:74:9 | x | stmts.go:77:9:77:9 | skip | +| stmts.go:77:9:77:9 | assignment to y | stmts.go:77:17:77:17 | y | +| stmts.go:77:9:77:9 | skip | stmts.go:77:14:77:14 | x | +| stmts.go:77:14:77:14 | x | stmts.go:77:9:77:9 | assignment to y | +| stmts.go:77:17:77:17 | y | stmts.go:77:21:77:22 | 19 | +| stmts.go:77:17:77:22 | ...-... | stmts.go:79:3:79:7 | test5 | +| stmts.go:77:21:77:22 | 19 | stmts.go:77:17:77:22 | ...-... | +| stmts.go:79:3:79:7 | test5 | stmts.go:79:9:79:13 | false | +| stmts.go:79:3:79:14 | call to test5 | stmts.go:82:9:82:9 | x | +| stmts.go:79:3:79:14 | call to test5 | stmts.go:107:1:107:1 | exit | +| stmts.go:79:9:79:13 | false | stmts.go:79:3:79:14 | call to test5 | +| stmts.go:82:9:82:9 | x | stmts.go:83:7:83:7 | 1 | +| stmts.go:82:9:82:9 | x | stmts.go:88:9:88:9 | x | +| stmts.go:83:2:83:8 | skip | stmts.go:88:9:88:9 | x | +| stmts.go:83:7:83:7 | 1 | stmts.go:83:7:83:7 | case 1 | +| stmts.go:83:7:83:7 | case 1 | stmts.go:83:2:83:8 | skip | +| stmts.go:83:7:83:7 | case 1 | stmts.go:84:7:84:7 | 2 | +| stmts.go:84:7:84:7 | 2 | stmts.go:84:7:84:7 | case 2 | +| stmts.go:84:7:84:7 | case 2 | stmts.go:84:10:84:10 | 3 | +| stmts.go:84:7:84:7 | case 2 | stmts.go:85:3:85:7 | test5 | +| stmts.go:84:10:84:10 | 3 | stmts.go:84:10:84:10 | case 3 | +| stmts.go:84:10:84:10 | case 3 | stmts.go:85:3:85:7 | test5 | +| stmts.go:84:10:84:10 | case 3 | stmts.go:88:9:88:9 | x | +| stmts.go:85:3:85:7 | test5 | stmts.go:85:9:85:12 | true | +| stmts.go:85:3:85:13 | call to test5 | stmts.go:88:9:88:9 | x | +| stmts.go:85:3:85:13 | call to test5 | stmts.go:107:1:107:1 | exit | +| stmts.go:85:9:85:12 | true | stmts.go:85:3:85:13 | call to test5 | +| stmts.go:88:9:88:9 | x | stmts.go:89:7:89:7 | 1 | +| stmts.go:88:9:88:9 | x | stmts.go:96:9:96:9 | x | +| stmts.go:89:7:89:7 | 1 | stmts.go:89:7:89:7 | case 1 | +| stmts.go:89:7:89:7 | case 1 | stmts.go:90:3:90:7 | test5 | +| stmts.go:89:7:89:7 | case 1 | stmts.go:92:7:92:11 | ...-... | +| stmts.go:90:3:90:7 | test5 | stmts.go:90:9:90:13 | false | +| stmts.go:90:3:90:14 | call to test5 | stmts.go:91:3:91:13 | skip | +| stmts.go:90:3:90:14 | call to test5 | stmts.go:107:1:107:1 | exit | +| stmts.go:90:9:90:13 | false | stmts.go:90:3:90:14 | call to test5 | +| stmts.go:91:3:91:13 | skip | stmts.go:93:3:93:7 | test5 | +| stmts.go:92:7:92:11 | ...-... | stmts.go:92:7:92:11 | case ...-... | +| stmts.go:92:7:92:11 | case ...-... | stmts.go:93:3:93:7 | test5 | +| stmts.go:92:7:92:11 | case ...-... | stmts.go:96:9:96:9 | x | +| stmts.go:93:3:93:7 | test5 | stmts.go:93:9:93:12 | true | +| stmts.go:93:3:93:13 | call to test5 | stmts.go:96:9:96:9 | x | +| stmts.go:93:3:93:13 | call to test5 | stmts.go:107:1:107:1 | exit | +| stmts.go:93:9:93:12 | true | stmts.go:93:3:93:13 | call to test5 | +| stmts.go:96:9:96:9 | x | stmts.go:98:7:98:7 | 2 | +| stmts.go:97:2:97:9 | skip | stmts.go:102:2:102:2 | true | +| stmts.go:98:7:98:7 | 2 | stmts.go:98:7:98:7 | case 2 | +| stmts.go:98:7:98:7 | case 2 | stmts.go:97:2:97:9 | skip | +| stmts.go:98:7:98:7 | case 2 | stmts.go:99:3:99:7 | test5 | +| stmts.go:99:3:99:7 | test5 | stmts.go:99:9:99:12 | true | +| stmts.go:99:3:99:13 | call to test5 | stmts.go:102:2:102:2 | true | +| stmts.go:99:3:99:13 | call to test5 | stmts.go:107:1:107:1 | exit | +| stmts.go:99:9:99:12 | true | stmts.go:99:3:99:13 | call to test5 | +| stmts.go:102:2:102:2 | true | stmts.go:105:7:105:10 | true | +| stmts.go:104:3:104:7 | skip | stmts.go:107:1:107:1 | exit | +| stmts.go:105:2:105:11 | skip | stmts.go:107:1:107:1 | exit | +| stmts.go:105:7:105:10 | case true | stmts.go:104:3:104:7 | skip | +| stmts.go:105:7:105:10 | case true | stmts.go:105:2:105:11 | skip | +| stmts.go:105:7:105:10 | true | stmts.go:105:7:105:10 | case true | +| stmts.go:110:1:110:1 | entry | stmts.go:110:12:110:12 | argument corresponding to x | +| stmts.go:110:1:123:1 | function declaration | stmts.go:126:6:126:11 | skip | +| stmts.go:110:6:110:10 | skip | stmts.go:110:1:123:1 | function declaration | +| stmts.go:110:12:110:12 | argument corresponding to x | stmts.go:110:12:110:12 | initialization of x | +| stmts.go:110:12:110:12 | initialization of x | stmts.go:111:9:111:9 | skip | +| stmts.go:111:9:111:9 | assignment to y | stmts.go:112:7:112:11 | case error | +| stmts.go:111:9:111:9 | assignment to y | stmts.go:119:9:119:9 | skip | +| stmts.go:111:9:111:9 | skip | stmts.go:111:14:111:14 | x | +| stmts.go:111:14:111:14 | x | stmts.go:111:14:111:21 | type assertion | +| stmts.go:111:14:111:21 | type assertion | stmts.go:111:9:111:9 | assignment to y | +| stmts.go:112:7:112:11 | case error | stmts.go:112:14:112:19 | case string | +| stmts.go:112:7:112:11 | case error | stmts.go:113:3:113:13 | selection of Println | +| stmts.go:112:14:112:19 | case string | stmts.go:113:3:113:13 | selection of Println | +| stmts.go:112:14:112:19 | case string | stmts.go:114:7:114:13 | case float32 | +| stmts.go:113:3:113:13 | selection of Println | stmts.go:113:15:113:15 | y | +| stmts.go:113:3:113:16 | call to Println | stmts.go:119:9:119:9 | skip | +| stmts.go:113:3:113:16 | call to Println | stmts.go:123:1:123:1 | exit | +| stmts.go:113:15:113:15 | y | stmts.go:113:3:113:16 | call to Println | +| stmts.go:114:7:114:13 | case float32 | stmts.go:115:3:115:7 | test5 | +| stmts.go:114:7:114:13 | case float32 | stmts.go:119:9:119:9 | skip | +| stmts.go:115:3:115:7 | test5 | stmts.go:115:9:115:12 | true | +| stmts.go:115:3:115:13 | call to test5 | stmts.go:116:3:116:7 | test5 | +| stmts.go:115:3:115:13 | call to test5 | stmts.go:123:1:123:1 | exit | +| stmts.go:115:9:115:12 | true | stmts.go:115:3:115:13 | call to test5 | +| stmts.go:116:3:116:7 | test5 | stmts.go:116:9:116:13 | false | +| stmts.go:116:3:116:14 | call to test5 | stmts.go:119:9:119:9 | skip | +| stmts.go:116:3:116:14 | call to test5 | stmts.go:123:1:123:1 | exit | +| stmts.go:116:9:116:13 | false | stmts.go:116:3:116:14 | call to test5 | +| stmts.go:119:9:119:9 | assignment to y | stmts.go:119:17:119:17 | y | +| stmts.go:119:9:119:9 | skip | stmts.go:119:14:119:14 | x | +| stmts.go:119:14:119:14 | x | stmts.go:119:9:119:9 | assignment to y | +| stmts.go:119:17:119:17 | y | stmts.go:119:17:119:24 | type assertion | +| stmts.go:119:17:119:24 | type assertion | stmts.go:121:3:121:7 | test5 | +| stmts.go:121:3:121:7 | test5 | stmts.go:121:9:121:13 | false | +| stmts.go:121:3:121:14 | call to test5 | stmts.go:123:1:123:1 | exit | +| stmts.go:121:9:121:13 | false | stmts.go:121:3:121:14 | call to test5 | +| stmts.go:126:1:126:1 | entry | stmts.go:126:13:126:13 | argument corresponding to f | +| stmts.go:126:1:128:1 | function declaration | stmts.go:131:6:131:11 | skip | +| stmts.go:126:6:126:11 | skip | stmts.go:126:1:128:1 | function declaration | +| stmts.go:126:13:126:13 | argument corresponding to f | stmts.go:126:13:126:13 | initialization of f | +| stmts.go:126:13:126:13 | initialization of f | stmts.go:127:5:127:5 | f | +| stmts.go:127:2:127:7 | go statement | stmts.go:128:1:128:1 | exit | +| stmts.go:127:5:127:5 | f | stmts.go:127:2:127:7 | go statement | +| stmts.go:131:1:131:1 | entry | stmts.go:131:13:131:14 | argument corresponding to xs | +| stmts.go:131:1:145:1 | function declaration | stmts.go:0:0:0:0 | exit | +| stmts.go:131:6:131:11 | skip | stmts.go:131:1:145:1 | function declaration | +| stmts.go:131:13:131:14 | argument corresponding to xs | stmts.go:131:13:131:14 | initialization of xs | +| stmts.go:131:13:131:14 | initialization of xs | stmts.go:132:17:132:18 | xs | +| stmts.go:132:2:137:2 | range statement[0] | stmts.go:132:6:132:6 | assignment to x | +| stmts.go:132:6:132:6 | assignment to x | stmts.go:133:6:133:6 | x | +| stmts.go:132:6:132:6 | skip | stmts.go:132:2:137:2 | range statement[0] | +| stmts.go:132:17:132:18 | next key-value pair in range | stmts.go:132:6:132:6 | skip | +| stmts.go:132:17:132:18 | next key-value pair in range | stmts.go:139:20:139:21 | xs | +| stmts.go:132:17:132:18 | xs | stmts.go:132:17:132:18 | next key-value pair in range | +| stmts.go:133:6:133:6 | x | stmts.go:133:10:133:10 | 5 | +| stmts.go:133:6:133:10 | ...>... | stmts.go:133:10:133:10 | ...>... is false | +| stmts.go:133:6:133:10 | ...>... | stmts.go:133:10:133:10 | ...>... is true | +| stmts.go:133:10:133:10 | 5 | stmts.go:133:6:133:10 | ...>... | +| stmts.go:133:10:133:10 | ...>... is false | stmts.go:136:3:136:11 | selection of Print | +| stmts.go:133:10:133:10 | ...>... is true | stmts.go:134:4:134:11 | skip | +| stmts.go:134:4:134:11 | skip | stmts.go:132:17:132:18 | next key-value pair in range | +| stmts.go:136:3:136:11 | selection of Print | stmts.go:136:13:136:13 | x | +| stmts.go:136:3:136:14 | call to Print | stmts.go:132:17:132:18 | next key-value pair in range | +| stmts.go:136:3:136:14 | call to Print | stmts.go:145:1:145:1 | exit | +| stmts.go:136:13:136:13 | x | stmts.go:136:3:136:14 | call to Print | +| stmts.go:139:2:141:2 | range statement[0] | stmts.go:139:2:141:2 | range statement[1] | +| stmts.go:139:2:141:2 | range statement[1] | stmts.go:139:6:139:6 | assignment to i | +| stmts.go:139:6:139:6 | assignment to i | stmts.go:139:9:139:9 | assignment to v | +| stmts.go:139:6:139:6 | skip | stmts.go:139:9:139:9 | skip | +| stmts.go:139:9:139:9 | assignment to v | stmts.go:140:3:140:11 | selection of Print | +| stmts.go:139:9:139:9 | skip | stmts.go:139:2:141:2 | range statement[0] | +| stmts.go:139:20:139:21 | next key-value pair in range | stmts.go:139:6:139:6 | skip | +| stmts.go:139:20:139:21 | next key-value pair in range | stmts.go:143:12:143:13 | xs | +| stmts.go:139:20:139:21 | xs | stmts.go:139:20:139:21 | next key-value pair in range | +| stmts.go:140:3:140:11 | selection of Print | stmts.go:140:13:140:13 | i | +| stmts.go:140:3:140:17 | call to Print | stmts.go:139:20:139:21 | next key-value pair in range | +| stmts.go:140:3:140:17 | call to Print | stmts.go:145:1:145:1 | exit | +| stmts.go:140:13:140:13 | i | stmts.go:140:16:140:16 | v | +| stmts.go:140:16:140:16 | v | stmts.go:140:3:140:17 | call to Print | +| stmts.go:143:12:143:13 | next key-value pair in range | stmts.go:143:15:144:2 | skip | +| stmts.go:143:12:143:13 | next key-value pair in range | stmts.go:145:1:145:1 | exit | +| stmts.go:143:12:143:13 | xs | stmts.go:143:12:143:13 | next key-value pair in range | +| stmts.go:143:15:144:2 | skip | stmts.go:143:12:143:13 | next key-value pair in range | diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.ql b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.ql new file mode 100644 index 00000000..d42f3fd1 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/ControlFlowNode_getASuccessor.ql @@ -0,0 +1,7 @@ +import go + +from ControlFlow::Node nd +where + // exclude code with build constraints to ensure platform-independent results + not nd.getFile().hasBuildConstraints() +select nd, nd.getASuccessor() diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/exprs.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/exprs.go new file mode 100644 index 00000000..be0540ae --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/exprs.go @@ -0,0 +1,95 @@ +package main + +type point struct{ x, y int } + +func test() point { + var i, j = 0, 1 + (2 + 3) + var k = i + 2*j + s := "k = " + string(k) + fn := func(a, b int, z float64) bool { return a*b < int(z) } + struct1 := struct{ x, y int }{} + struct2 := struct { + x int + y bool + }{k, fn(i, j, 3/14)} + struct3 := struct{ x, y int }{y: struct1.x, x: struct2.x} + arr1 := [1]int{struct3.x} + arr2 := [...]int{struct3.x, 2: arr1[0]} + slc := []string{s, s} + mp := map[string]int{slc[0]: arr2[1]} + slc2 := slc[1:2:3] + slc3 := slc2[:2:3] + slc4 := slc3[0:2] + slc5 := slc4[0:] + slc6 := slc5[:2] + return point{mp[s], len(slc6[0])} +} + +func test2(arg interface{}) int { + return arg.(point).x +} + +func test3(arg interface{}) int { + if p, ok := arg.(point); ok { + return p.x + } + return -1 +} + +func test4(arg interface{}) int { + var p point + var ok bool + p, ok = arg.(point) + if ok { + return p.x + } + return -1 +} + +func sum(xs []int) (res int) { + for i := 0; i < len(xs); i++ { + res += xs[i] + } + return +} + +func sum2(xs ...int) int { + return sum(xs) +} + +func ints() []int { + return []int{1, 2, 3} +} + +var s = sum(ints()) +var s2 = sum2(ints()...) + +func add(x, y int) int { + return x + y +} + +func gen() (int, int) { + return 1, 2 +} + +var s3 = add(gen()) + +func short(x, y, z bool) bool { + return !(x && y) || z +} + +func recvOrPanic(ch chan int) int { + val, ok := <-ch + if ok { + return val + } + panic("No value") +} + +const one = 1 + +var a = []int{0 + one: 2} + +func short2(x, y, z bool) bool { + return (x && y) || z +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/hello.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/hello.go new file mode 100644 index 00000000..a30ba7c6 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/hello.go @@ -0,0 +1,9 @@ +package main + +import "fmt" + +const message = "Hello, world!" + +func sayHello() { + fmt.Println(message) +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/linux.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/linux.go new file mode 100644 index 00000000..66c40b54 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/linux.go @@ -0,0 +1,5 @@ +// +build linux + +package main + +const linux = true diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/main.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/main.go new file mode 100644 index 00000000..a001ce37 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/main.go @@ -0,0 +1,86 @@ +package main + +import ( + "fmt" + "math/rand" +) + +func cond() bool { + return rand.Float64() >= 0.5 +} + +func main() { + var x int + y := 23 + fmt.Print(x, y) + if cond() { + y += 19 + } + fmt.Print(x, y) + if cond() { + x = y + } + fmt.Print(x, y) +} + +func foo(x int) (int, int) { + a, b := x, 0 + if cond() { + a, b = b, a + } + return a, b +} + +func bump(x *int) { + *x += 19 +} + +func bar() { + x := 23 + ptr := &x + if cond() { + bump(ptr) + } + fmt.Print(x) +} + +func baz() (result int) { + result = 42 + return +} + +func baz2() (result int) { + return +} + +func loops() { + var x int + for cond() { + x = 2 + } + fmt.Print(x) + + y := 1 + for i := 0; ; i++ { + if cond() { + break + } + y = 2 + } + fmt.Print(y) + + z := 1 + for i := 0; ; i++ { + z = 2 + if cond() { + break + } + } + fmt.Print(z) +} + +func multiRes() (a int, b float32) { + x := 23 + x, a = x+19, x + return +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/nonlinux.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/nonlinux.go new file mode 100644 index 00000000..3347a26f --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/nonlinux.go @@ -0,0 +1,5 @@ +// +build !linux + +package main + +const linux = false diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts.go new file mode 100644 index 00000000..f9fddbd5 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts.go @@ -0,0 +1,145 @@ +package main + +import "fmt" + +// NOTE: after auto-formatting this file, make sure to put back the empty statement on line 15 below + +// simple statements and for loops +func test5(b bool) { + { + if !b { + goto outer + } + { + } + ; // empty statement + } + + fmt.Println("Hi") + +outer: + for true { + for i := 0; i < 10; i++ { + if j := i - 1; j > 5 { + break outer + } else if i < 3 { + break + } else if i != 9 { + continue outer + } else if i >= 4 { + goto outer + } else { + continue + } + } + } + + k := 9 + for ; ; k++ { + goto outer + } +} + +// select +func test6(ch1 chan int, ch2 chan float32) { + var a [1]float32 + var w bool + + select { + case <-ch1: + fmt.Println("Heard from ch1") + case a[0], w = <-ch2: + fmt.Println(a) + fmt.Println(w) + default: + fmt.Println() + case ch1 <- 42: + } + + select {} +} + +// defer +func test7(x int) int { + if x > 0 { + defer func() { fmt.Println(x) }() + } else { + defer func() { fmt.Println(-x) }() + } + return 42 +} + +// expression switch +func test8(x int) { + switch x { + } + + switch y := x; y - 19 { + default: + test5(false) + } + + switch x { + case 1: + case 2, 3: + test5(true) + } + + switch x { + case 1: + test5(false) + fallthrough + case 2 - 5: + test5(true) + } + + switch x { + default: + case 2: + test5(true) + } + + switch { + default: + break + case true: + } +} + +// type switch +func test9(x interface{}) { + switch y := x.(type) { + case error, string: + fmt.Println(y) + case float32: + test5(true) + test5(false) + } + + switch y := x; y.(type) { + default: + test5(false) + } +} + +// go +func test10(f func()) { + go f() +} + +// more loops +func test11(xs []int) { + for x := range xs { + if x > 5 { + continue + } + fmt.Print(x) + } + + for i, v := range xs { + fmt.Print(i, v) + } + + for range xs { + } +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts2.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts2.go new file mode 100644 index 00000000..9127b86d --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts2.go @@ -0,0 +1,28 @@ +package main + +func test12() int { + _ = test7(0) + var _ = test7(1) + return 2 +} + +func test13() int { + _, x := gen() + var _, y = gen() + return x + y +} + +func test14(ch chan int) int { + select { + case _ = <-ch: + case x, _ := <-ch: + return x + case _, y := <-ch: + if y { + break + } + return 0 + case _, _ = <-ch: + } + return 1 +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts3.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts3.go new file mode 100644 index 00000000..8d73120a --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts3.go @@ -0,0 +1,20 @@ +package main + +import "flag" + +func test15() int { + const ( + red = iota + green + blue + ) + return red + green - blue +} + +func test16(x *int) { + *x = 42 +} + +func test17() { + flag.Usage = func() {} +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts4.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts4.go new file mode 100644 index 00000000..e2279305 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts4.go @@ -0,0 +1,5 @@ +package main + +var _ int + +func test18(x int) float32 diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts5.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts5.go new file mode 100644 index 00000000..131b71dd --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts5.go @@ -0,0 +1,12 @@ +package main + +type myint struct { + val int +} + +func (me myint) bump(other int) { + me.val += other +} + +func (myint) foo() { +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts6.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts6.go new file mode 100644 index 00000000..e48cb2bc --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts6.go @@ -0,0 +1,8 @@ +package main + +func test19() int { + if true { + return 42 + } + return 23 +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts7.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts7.go new file mode 100644 index 00000000..f44b8d53 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts7.go @@ -0,0 +1,28 @@ +package main + +import "fmt" + +func recoverPanic() { + blah := recover() + fmt.Println("recovered: ", blah) +} + +func canRecover() { + defer recoverPanic() + panic("") +} + +type Callback struct { + fn func() bool +} + +func (methods *Callback) run() { + methods.fn() +} + +func defertest(callback Callback) bool { + defer callback.fn() + defer (&callback).fn() + fmt.Println("print something") + return false +} diff --git a/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts8.go b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts8.go new file mode 100644 index 00000000..3b349641 --- /dev/null +++ b/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/stmts8.go @@ -0,0 +1,14 @@ +package main + +func test20(x int) (int, int) { + y := x >> 5 + z := x % (1) + return z, y % 13 +} + +func isLinux() bool { + if linux { + return true + } + return false +} diff --git a/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.expected b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.expected new file mode 100644 index 00000000..97d79eb5 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.expected @@ -0,0 +1,133 @@ +| main.go:3:12:3:12 | argument corresponding to x | main.go:3:12:3:12 | definition of x | +| main.go:3:12:3:12 | definition of x | main.go:5:5:5:5 | x | +| main.go:3:12:3:12 | definition of x | main.go:6:7:6:7 | x | +| main.go:3:12:3:12 | definition of x | main.go:8:8:8:8 | x | +| main.go:3:12:3:12 | definition of x | main.go:10:7:10:7 | x | +| main.go:3:12:3:12 | definition of x | main.go:10:22:10:22 | x | +| main.go:3:19:3:20 | argument corresponding to fn | main.go:3:19:3:20 | definition of fn | +| main.go:3:19:3:20 | definition of fn | main.go:10:24:10:25 | fn | +| main.go:6:3:6:3 | definition of y | main.go:10:2:10:2 | y = phi(def@6:3, def@8:3) | +| main.go:6:7:6:7 | x | main.go:6:3:6:3 | definition of y | +| main.go:8:3:8:3 | definition of y | main.go:10:2:10:2 | y = phi(def@6:3, def@8:3) | +| main.go:8:7:8:8 | -... | main.go:8:3:8:3 | definition of y | +| main.go:10:2:10:2 | definition of z | main.go:11:14:11:14 | z | +| main.go:10:2:10:2 | y = phi(def@6:3, def@8:3) | main.go:10:12:10:12 | y | +| main.go:10:2:10:2 | y = phi(def@6:3, def@8:3) | main.go:10:17:10:17 | y | +| main.go:10:7:10:12 | ...<=... | main.go:10:7:10:27 | ...&&... | +| main.go:10:7:10:27 | ...&&... | main.go:10:2:10:2 | definition of z | +| main.go:10:17:10:27 | ...>=... | main.go:10:7:10:27 | ...&&... | +| main.go:11:14:11:14 | z | main.go:11:9:11:15 | type conversion | +| main.go:15:2:15:4 | definition of acc | main.go:16:9:16:9 | capture variable acc | +| main.go:15:9:15:9 | 0 | main.go:15:2:15:4 | definition of acc | +| main.go:16:9:16:9 | capture variable acc | main.go:17:3:17:5 | acc | +| main.go:17:3:17:7 | definition of acc | main.go:16:9:16:9 | capture variable acc | +| main.go:17:3:17:7 | definition of acc | main.go:18:10:18:12 | acc | +| main.go:17:3:17:7 | rhs of increment statement | main.go:17:3:17:7 | definition of acc | +| main.go:22:12:22:12 | argument corresponding to b | main.go:22:12:22:12 | definition of b | +| main.go:22:12:22:12 | definition of b | main.go:23:5:23:5 | b | +| main.go:22:20:22:20 | argument corresponding to x | main.go:22:20:22:20 | definition of x | +| main.go:22:20:22:20 | definition of x | main.go:24:10:24:10 | x | +| main.go:22:20:22:20 | definition of x | main.go:26:11:26:11 | x | +| main.go:24:10:24:10 | x | main.go:24:10:24:19 | type assertion | +| main.go:26:2:26:2 | definition of n | main.go:27:11:27:11 | n | +| main.go:26:2:26:17 | ... := ...[0] | main.go:26:2:26:2 | definition of n | +| main.go:26:2:26:17 | ... := ...[1] | main.go:26:5:26:6 | definition of ok | +| main.go:26:5:26:6 | definition of ok | main.go:27:5:27:6 | ok | +| main.go:26:11:26:11 | x | main.go:26:11:26:17 | type assertion | +| main.go:34:2:34:6 | test1 | main.go:3:1:12:1 | function test1 | +| main.go:34:8:34:12 | test2 | main.go:14:1:20:1 | function test2 | +| main.go:34:19:34:23 | test2 | main.go:14:1:20:1 | function test2 | +| strings.go:8:12:8:12 | argument corresponding to s | strings.go:8:12:8:12 | definition of s | +| strings.go:8:12:8:12 | definition of s | strings.go:9:24:9:24 | s | +| strings.go:8:12:8:12 | definition of s | strings.go:10:27:10:27 | s | +| strings.go:9:2:9:3 | definition of s2 | strings.go:11:20:11:21 | s2 | +| strings.go:9:2:9:3 | definition of s2 | strings.go:11:48:11:49 | s2 | +| strings.go:9:8:9:22 | selection of Replace | file://:0:0:0:0 | function Replace | +| strings.go:9:8:9:38 | call to Replace | strings.go:9:2:9:3 | definition of s2 | +| strings.go:10:2:10:3 | definition of s3 | strings.go:11:24:11:25 | s3 | +| strings.go:10:2:10:3 | definition of s3 | strings.go:11:67:11:68 | s3 | +| strings.go:10:8:10:25 | selection of ReplaceAll | file://:0:0:0:0 | function ReplaceAll | +| strings.go:10:8:10:42 | call to ReplaceAll | strings.go:10:2:10:3 | definition of s3 | +| strings.go:11:9:11:18 | selection of Sprint | file://:0:0:0:0 | function Sprint | +| strings.go:11:30:11:40 | selection of Sprintf | file://:0:0:0:0 | function Sprintf | +| strings.go:11:54:11:65 | selection of Sprintln | file://:0:0:0:0 | function Sprintln | +| url.go:8:12:8:12 | argument corresponding to b | url.go:8:12:8:12 | definition of b | +| url.go:8:12:8:12 | definition of b | url.go:11:5:11:5 | b | +| url.go:8:20:8:20 | argument corresponding to s | url.go:8:20:8:20 | definition of s | +| url.go:8:20:8:20 | definition of s | url.go:12:46:12:46 | s | +| url.go:8:20:8:20 | definition of s | url.go:14:48:14:48 | s | +| url.go:12:3:12:5 | definition of res | url.go:16:5:16:5 | res = phi(def@12:3, def@14:3) | +| url.go:12:3:12:48 | ... = ...[0] | url.go:12:3:12:5 | definition of res | +| url.go:12:3:12:48 | ... = ...[1] | url.go:12:8:12:10 | definition of err | +| url.go:12:8:12:10 | definition of err | url.go:16:5:16:5 | err = phi(def@12:8, def@14:8) | +| url.go:12:14:12:29 | selection of PathUnescape | file://:0:0:0:0 | function PathUnescape | +| url.go:12:31:12:44 | selection of PathEscape | file://:0:0:0:0 | function PathEscape | +| url.go:14:3:14:5 | definition of res | url.go:16:5:16:5 | res = phi(def@12:3, def@14:3) | +| url.go:14:3:14:50 | ... = ...[0] | url.go:14:3:14:5 | definition of res | +| url.go:14:3:14:50 | ... = ...[1] | url.go:14:8:14:10 | definition of err | +| url.go:14:8:14:10 | definition of err | url.go:16:5:16:5 | err = phi(def@12:8, def@14:8) | +| url.go:14:14:14:30 | selection of QueryUnescape | file://:0:0:0:0 | function QueryUnescape | +| url.go:14:32:14:46 | selection of QueryEscape | file://:0:0:0:0 | function QueryEscape | +| url.go:16:5:16:5 | err = phi(def@12:8, def@14:8) | url.go:16:5:16:7 | err | +| url.go:16:5:16:5 | res = phi(def@12:3, def@14:3) | url.go:19:9:19:11 | res | +| url.go:22:12:22:12 | argument corresponding to i | url.go:22:12:22:12 | definition of i | +| url.go:22:12:22:12 | definition of i | url.go:24:5:24:5 | i | +| url.go:22:19:22:19 | argument corresponding to s | url.go:22:19:22:19 | definition of s | +| url.go:22:19:22:19 | definition of s | url.go:23:20:23:20 | s | +| url.go:22:19:22:19 | definition of s | url.go:27:29:27:29 | s | +| url.go:23:2:23:2 | definition of u | url.go:25:10:25:10 | u | +| url.go:23:2:23:21 | ... := ...[0] | url.go:23:2:23:2 | definition of u | +| url.go:23:10:23:18 | selection of Parse | file://:0:0:0:0 | function Parse | +| url.go:27:2:27:2 | definition of u | url.go:28:14:28:14 | u | +| url.go:27:2:27:2 | definition of u | url.go:29:14:29:14 | u | +| url.go:27:2:27:2 | definition of u | url.go:30:11:30:11 | u | +| url.go:27:2:27:2 | definition of u | url.go:32:9:32:9 | u | +| url.go:27:2:27:30 | ... = ...[0] | url.go:27:2:27:2 | definition of u | +| url.go:27:9:27:27 | selection of ParseRequestURI | file://:0:0:0:0 | function ParseRequestURI | +| url.go:28:2:28:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:28:14:28:26 | selection of EscapedPath | file://:0:0:0:0 | function EscapedPath | +| url.go:29:2:29:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:29:14:29:23 | selection of Hostname | file://:0:0:0:0 | function Hostname | +| url.go:30:2:30:3 | definition of bs | url.go:31:14:31:15 | bs | +| url.go:30:2:30:27 | ... := ...[0] | url.go:30:2:30:3 | definition of bs | +| url.go:30:11:30:25 | selection of MarshalBinary | file://:0:0:0:0 | function MarshalBinary | +| url.go:31:2:31:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:32:2:32:2 | definition of u | url.go:33:14:33:14 | u | +| url.go:32:2:32:2 | definition of u | url.go:34:14:34:14 | u | +| url.go:32:2:32:2 | definition of u | url.go:35:14:35:14 | u | +| url.go:32:2:32:2 | definition of u | url.go:36:6:36:6 | u | +| url.go:32:2:32:2 | definition of u | url.go:36:25:36:25 | u | +| url.go:32:2:32:23 | ... = ...[0] | url.go:32:2:32:2 | definition of u | +| url.go:32:9:32:15 | selection of Parse | file://:0:0:0:0 | function Parse | +| url.go:33:2:33:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:33:14:33:19 | selection of Port | file://:0:0:0:0 | function Port | +| url.go:34:2:34:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:34:14:34:20 | selection of Query | file://:0:0:0:0 | function Query | +| url.go:35:2:35:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:35:14:35:25 | selection of RequestURI | file://:0:0:0:0 | function RequestURI | +| url.go:36:2:36:2 | definition of u | url.go:37:9:37:9 | u | +| url.go:36:6:36:23 | selection of ResolveReference | file://:0:0:0:0 | function ResolveReference | +| url.go:36:6:36:26 | call to ResolveReference | url.go:36:2:36:2 | definition of u | +| url.go:41:8:41:15 | selection of User | file://:0:0:0:0 | function User | +| url.go:42:2:42:3 | definition of ui | url.go:43:11:43:12 | ui | +| url.go:42:2:42:3 | definition of ui | url.go:45:14:45:15 | ui | +| url.go:42:2:42:3 | definition of ui | url.go:46:9:46:10 | ui | +| url.go:42:7:42:22 | selection of UserPassword | file://:0:0:0:0 | function UserPassword | +| url.go:42:7:42:38 | call to UserPassword | url.go:42:2:42:3 | definition of ui | +| url.go:43:2:43:3 | definition of pw | url.go:44:14:44:15 | pw | +| url.go:43:2:43:23 | ... := ...[0] | url.go:43:2:43:3 | definition of pw | +| url.go:43:11:43:21 | selection of Password | file://:0:0:0:0 | function Password | +| url.go:44:2:44:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:45:2:45:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:45:14:45:24 | selection of Username | file://:0:0:0:0 | function Username | +| url.go:49:12:49:12 | argument corresponding to q | url.go:49:12:49:12 | definition of q | +| url.go:49:12:49:12 | definition of q | url.go:50:25:50:25 | q | +| url.go:50:2:50:2 | definition of v | url.go:51:14:51:14 | v | +| url.go:50:2:50:2 | definition of v | url.go:52:14:52:14 | v | +| url.go:50:2:50:2 | definition of v | url.go:53:9:53:9 | v | +| url.go:50:2:50:26 | ... := ...[0] | url.go:50:2:50:2 | definition of v | +| url.go:50:10:50:23 | selection of ParseQuery | file://:0:0:0:0 | function ParseQuery | +| url.go:51:2:51:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:51:14:51:21 | selection of Encode | file://:0:0:0:0 | function Encode | +| url.go:52:2:52:12 | selection of Println | file://:0:0:0:0 | function Println | +| url.go:52:14:52:18 | selection of Get | file://:0:0:0:0 | function Get | diff --git a/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.ql b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.ql new file mode 100644 index 00000000..979c4fd6 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalFlowStep.ql @@ -0,0 +1,5 @@ +import go + +from DataFlow::Node nd, DataFlow::Node succ +where DataFlow::localFlowStep(nd, succ) +select nd, succ diff --git a/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalTaintStep.expected b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalTaintStep.expected new file mode 100644 index 00000000..7456fdd4 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalTaintStep.expected @@ -0,0 +1,57 @@ +| main.go:10:22:10:22 | x | main.go:10:22:10:27 | ...+... | +| main.go:10:24:10:27 | call to fn | main.go:10:22:10:27 | ...+... | +| main.go:26:11:26:17 | type assertion | main.go:26:2:26:17 | ... := ...[0] | +| main.go:26:11:26:17 | type assertion | main.go:26:2:26:17 | ... := ...[1] | +| strings.go:9:24:9:24 | s | strings.go:9:8:9:38 | call to Replace | +| strings.go:9:32:9:34 | "_" | strings.go:9:8:9:38 | call to Replace | +| strings.go:10:27:10:27 | s | strings.go:10:8:10:42 | call to ReplaceAll | +| strings.go:10:35:10:41 | "&" | strings.go:10:8:10:42 | call to ReplaceAll | +| strings.go:11:9:11:26 | call to Sprint | strings.go:11:9:11:50 | ...+... | +| strings.go:11:9:11:50 | ...+... | strings.go:11:9:11:69 | ...+... | +| strings.go:11:20:11:21 | s2 | strings.go:11:9:11:26 | call to Sprint | +| strings.go:11:24:11:25 | s3 | strings.go:11:9:11:26 | call to Sprint | +| strings.go:11:30:11:50 | call to Sprintf | strings.go:11:9:11:50 | ...+... | +| strings.go:11:42:11:45 | "%q" | strings.go:11:30:11:50 | call to Sprintf | +| strings.go:11:48:11:49 | s2 | strings.go:11:30:11:50 | call to Sprintf | +| strings.go:11:54:11:69 | call to Sprintln | strings.go:11:9:11:69 | ...+... | +| strings.go:11:67:11:68 | s3 | strings.go:11:54:11:69 | call to Sprintln | +| url.go:12:14:12:48 | call to PathUnescape | url.go:12:3:12:48 | ... = ...[0] | +| url.go:12:14:12:48 | call to PathUnescape | url.go:12:3:12:48 | ... = ...[1] | +| url.go:12:31:12:47 | call to PathEscape | url.go:12:3:12:48 | ... = ...[0] | +| url.go:12:46:12:46 | s | url.go:12:31:12:47 | call to PathEscape | +| url.go:14:14:14:50 | call to QueryUnescape | url.go:14:3:14:50 | ... = ...[0] | +| url.go:14:14:14:50 | call to QueryUnescape | url.go:14:3:14:50 | ... = ...[1] | +| url.go:14:32:14:49 | call to QueryEscape | url.go:14:3:14:50 | ... = ...[0] | +| url.go:14:48:14:48 | s | url.go:14:32:14:49 | call to QueryEscape | +| url.go:23:10:23:21 | call to Parse | url.go:23:2:23:21 | ... := ...[0] | +| url.go:23:10:23:21 | call to Parse | url.go:23:2:23:21 | ... := ...[1] | +| url.go:23:20:23:20 | s | url.go:23:2:23:21 | ... := ...[0] | +| url.go:27:9:27:30 | call to ParseRequestURI | url.go:27:2:27:30 | ... = ...[0] | +| url.go:27:9:27:30 | call to ParseRequestURI | url.go:27:2:27:30 | ... = ...[1] | +| url.go:27:29:27:29 | s | url.go:27:2:27:30 | ... = ...[0] | +| url.go:28:14:28:14 | u | url.go:28:14:28:28 | call to EscapedPath | +| url.go:29:14:29:14 | u | url.go:29:14:29:25 | call to Hostname | +| url.go:30:11:30:11 | u | url.go:30:2:30:27 | ... := ...[0] | +| url.go:30:11:30:27 | call to MarshalBinary | url.go:30:2:30:27 | ... := ...[0] | +| url.go:30:11:30:27 | call to MarshalBinary | url.go:30:2:30:27 | ... := ...[1] | +| url.go:32:9:32:9 | u | url.go:32:2:32:23 | ... = ...[0] | +| url.go:32:9:32:23 | call to Parse | url.go:32:2:32:23 | ... = ...[0] | +| url.go:32:9:32:23 | call to Parse | url.go:32:2:32:23 | ... = ...[1] | +| url.go:32:17:32:22 | "/foo" | url.go:32:2:32:23 | ... = ...[0] | +| url.go:33:14:33:14 | u | url.go:33:14:33:21 | call to Port | +| url.go:34:14:34:14 | u | url.go:34:14:34:22 | call to Query | +| url.go:35:14:35:14 | u | url.go:35:14:35:27 | call to RequestURI | +| url.go:36:6:36:6 | u | url.go:36:6:36:26 | call to ResolveReference | +| url.go:36:25:36:25 | u | url.go:36:6:36:26 | call to ResolveReference | +| url.go:41:17:41:20 | "me" | url.go:41:8:41:21 | call to User | +| url.go:42:24:42:27 | "me" | url.go:42:7:42:38 | call to UserPassword | +| url.go:42:30:42:37 | "secret" | url.go:42:7:42:38 | call to UserPassword | +| url.go:43:11:43:12 | ui | url.go:43:2:43:23 | ... := ...[0] | +| url.go:43:11:43:23 | call to Password | url.go:43:2:43:23 | ... := ...[0] | +| url.go:43:11:43:23 | call to Password | url.go:43:2:43:23 | ... := ...[1] | +| url.go:45:14:45:15 | ui | url.go:45:14:45:26 | call to Username | +| url.go:50:10:50:26 | call to ParseQuery | url.go:50:2:50:26 | ... := ...[0] | +| url.go:50:10:50:26 | call to ParseQuery | url.go:50:2:50:26 | ... := ...[1] | +| url.go:50:25:50:25 | q | url.go:50:2:50:26 | ... := ...[0] | +| url.go:51:14:51:14 | v | url.go:51:14:51:23 | call to Encode | +| url.go:52:14:52:14 | v | url.go:52:14:52:26 | call to Get | diff --git a/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalTaintStep.ql b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalTaintStep.ql new file mode 100644 index 00000000..f9f3b594 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalTaintStep.ql @@ -0,0 +1,8 @@ +import go + +from DataFlow::Node nd, DataFlow::Node succ +where + TaintTracking::localTaintStep(nd, succ) and + // exclude data-flow steps + not DataFlow::localFlowStep(nd, succ) +select nd, succ diff --git a/ql/test/library-tests/semmle/go/dataflow/FlowSteps/main.go b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/main.go new file mode 100644 index 00000000..9bcde307 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/main.go @@ -0,0 +1,35 @@ +package main + +func test1(x int, fn func() int) bool { + var y int + if x >= 0 { + y = x + } else { + y = -x + } + z := x <= y && y >= x+fn() + return bool(z) +} + +func test2() func() int { + acc := 0 + return func() int { + acc++ + return acc + } +} + +func test3(b bool, x interface{}) string { + if b { + return x.(string) + } + n, ok := x.(int) + if ok && n > 10 { + return "yes" + } + return "no" +} + +func main() { + test1(test2()(), test2()) +} diff --git a/ql/test/library-tests/semmle/go/dataflow/FlowSteps/strings.go b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/strings.go new file mode 100644 index 00000000..7356a507 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/strings.go @@ -0,0 +1,12 @@ +package main + +import ( + "fmt" + "strings" +) + +func test4(s string) string { + s2 := strings.Replace(s, " ", "_", 1) + s3 := strings.ReplaceAll(s, "&", "&") + return fmt.Sprint(s2, s3) + fmt.Sprintf("%q", s2) + fmt.Sprintln(s3) +} diff --git a/ql/test/library-tests/semmle/go/dataflow/FlowSteps/url.go b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/url.go new file mode 100644 index 00000000..da3df63f --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FlowSteps/url.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "net/url" +) + +func func5(b bool, s string) string { + var res string + var err error + if b { + res, err = url.PathUnescape(url.PathEscape(s)) + } else { + res, err = url.QueryUnescape(url.QueryEscape(s)) + } + if err != nil { + return "" + } + return res +} + +func func6(i int, s string) *url.URL { + u, _ := url.Parse(s) + if i == 0 { + return u + } + u, _ = url.ParseRequestURI(s) + fmt.Println(u.EscapedPath()) + fmt.Println(u.Hostname()) + bs, _ := u.MarshalBinary() + fmt.Println(bs) + u, _ = u.Parse("/foo") + fmt.Println(u.Port()) + fmt.Println(u.Query()) + fmt.Println(u.RequestURI()) + u = u.ResolveReference(u) + return u +} + +func func7() *url.Userinfo { + ui := url.User("me") + ui = url.UserPassword("me", "secret") + pw, _ := ui.Password() + fmt.Println(pw) + fmt.Println(ui.Username()) + return ui +} + +func func8(q string) url.Values { + v, _ := url.ParseQuery(q) + fmt.Println(v.Encode()) + fmt.Println(v.Get("page")) + return v +} diff --git a/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getEntryNode.expected b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getEntryNode.expected new file mode 100644 index 00000000..316a5f82 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getEntryNode.expected @@ -0,0 +1,13 @@ +| parameter 0 | main.go:51:2:51:14 | call to op | main.go:51:5:51:7 | "+" | +| parameter 0 | main.go:53:2:53:22 | call to op2 | main.go:53:6:53:8 | "-" | +| parameter 0 | main.go:55:2:55:27 | call to Printf | main.go:55:13:55:20 | "%d, %d" | +| parameter 0 | main.go:57:2:57:27 | call to Printf | main.go:57:13:57:20 | "%d, %d" | +| parameter 1 | main.go:51:2:51:14 | call to op | main.go:51:10:51:10 | 1 | +| parameter 1 | main.go:53:2:53:22 | call to op2 | main.go:53:11:53:11 | 2 | +| parameter 1 | main.go:55:2:55:27 | call to Printf | main.go:55:23:55:23 | x | +| parameter 1 | main.go:57:2:57:27 | call to Printf | main.go:57:23:57:23 | x | +| parameter 2 | main.go:51:2:51:14 | call to op | main.go:51:13:51:13 | 1 | +| parameter 2 | main.go:53:2:53:22 | call to op2 | main.go:53:14:53:21 | call to bump | +| parameter 2 | main.go:55:2:55:27 | call to Printf | main.go:55:26:55:26 | y | +| parameter 2 | main.go:57:2:57:27 | call to Printf | main.go:57:26:57:26 | y | +| receiver | main.go:53:14:53:21 | call to bump | main.go:53:14:53:14 | c | diff --git a/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getEntryNode.ql b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getEntryNode.ql new file mode 100644 index 00000000..ad032dca --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getEntryNode.ql @@ -0,0 +1,4 @@ +import go + +from FunctionInput inp, DataFlow::CallNode c +select inp, c, inp.getEntryNode(c) \ No newline at end of file diff --git a/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.expected b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.expected new file mode 100644 index 00000000..92e681a7 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.expected @@ -0,0 +1,8 @@ +| parameter 0 | main.go:5:1:11:1 | function declaration | main.go:5:9:5:10 | definition of op | +| parameter 0 | main.go:13:1:20:1 | function declaration | main.go:13:10:13:11 | definition of op | +| parameter 0 | main.go:40:1:48:1 | function declaration | main.go:40:12:40:12 | definition of b | +| parameter 1 | main.go:5:1:11:1 | function declaration | main.go:5:20:5:20 | definition of x | +| parameter 1 | main.go:13:1:20:1 | function declaration | main.go:13:21:13:21 | definition of x | +| parameter 2 | main.go:5:1:11:1 | function declaration | main.go:5:27:5:27 | definition of y | +| parameter 2 | main.go:13:1:20:1 | function declaration | main.go:13:28:13:28 | definition of y | +| receiver | main.go:26:1:29:1 | function declaration | main.go:26:7:26:7 | definition of c | diff --git a/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.ql b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.ql new file mode 100644 index 00000000..06b151b5 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionInput_getExitNode.ql @@ -0,0 +1,4 @@ +import go + +from FunctionInput inp, FuncDef f +select inp, f, inp.getExitNode(f) \ No newline at end of file diff --git a/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getEntryNode.expected b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getEntryNode.expected new file mode 100644 index 00000000..a9bde481 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getEntryNode.expected @@ -0,0 +1,19 @@ +| result | main.go:5:1:11:1 | function declaration | main.go:7:10:7:14 | ...+... | +| result | main.go:5:1:11:1 | function declaration | main.go:9:10:9:14 | ...-... | +| result | main.go:13:1:20:1 | function declaration | main.go:13:36:13:38 | zero value for res | +| result | main.go:13:1:20:1 | function declaration | main.go:15:9:15:13 | ...+... | +| result | main.go:13:1:20:1 | function declaration | main.go:17:10:17:14 | ...-... | +| result | main.go:26:1:29:1 | function declaration | main.go:28:9:28:15 | selection of count | +| result 0 | main.go:31:1:33:1 | function declaration | main.go:32:9:32:10 | 23 | +| result 0 | main.go:35:1:38:1 | function declaration | main.go:35:15:35:15 | zero value for x | +| result 0 | main.go:35:1:38:1 | function declaration | main.go:36:13:36:14 | 23 | +| result 0 | main.go:40:1:48:1 | function declaration | main.go:40:21:40:23 | zero value for int | +| result 0 | main.go:40:1:48:1 | function declaration | main.go:45:10:45:10 | 0 | +| result 0 | main.go:40:1:48:1 | function declaration | main.go:47:9:47:9 | 0 | +| result 1 | main.go:31:1:33:1 | function declaration | main.go:32:13:32:14 | 42 | +| result 1 | main.go:35:1:38:1 | function declaration | main.go:35:22:35:22 | zero value for y | +| result 1 | main.go:35:1:38:1 | function declaration | main.go:36:9:36:10 | 42 | +| result 1 | main.go:40:1:48:1 | function declaration | main.go:40:26:40:26 | zero value for y | +| result 1 | main.go:40:1:48:1 | function declaration | main.go:42:3:42:5 | rhs of increment statement | +| result 1 | main.go:40:1:48:1 | function declaration | main.go:45:13:45:13 | 1 | +| result 1 | main.go:40:1:48:1 | function declaration | main.go:47:12:47:12 | 4 | diff --git a/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getEntryNode.ql b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getEntryNode.ql new file mode 100644 index 00000000..c37b614f --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getEntryNode.ql @@ -0,0 +1,4 @@ +import go + +from FunctionOutput outp, FuncDef f +select outp, f, outp.getEntryNode(f) \ No newline at end of file diff --git a/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getExitNode.expected b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getExitNode.expected new file mode 100644 index 00000000..19c2533a --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getExitNode.expected @@ -0,0 +1,7 @@ +| result | main.go:51:2:51:14 | call to op | main.go:51:2:51:14 | call to op | +| result | main.go:53:2:53:22 | call to op2 | main.go:53:2:53:22 | call to op2 | +| result | main.go:53:14:53:21 | call to bump | main.go:53:14:53:21 | call to bump | +| result 0 | main.go:54:10:54:15 | call to test | main.go:54:2:54:15 | ... := ...[0] | +| result 0 | main.go:56:9:56:15 | call to test2 | main.go:56:2:56:15 | ... = ...[0] | +| result 1 | main.go:54:10:54:15 | call to test | main.go:54:2:54:15 | ... := ...[1] | +| result 1 | main.go:56:9:56:15 | call to test2 | main.go:56:2:56:15 | ... = ...[1] | diff --git a/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getExitNode.ql b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getExitNode.ql new file mode 100644 index 00000000..cfddfcfe --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/FunctionOutput_getExitNode.ql @@ -0,0 +1,4 @@ +import go + +from FunctionOutput outp, DataFlow::CallNode c +select outp, c, outp.getExitNode(c) \ No newline at end of file diff --git a/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/main.go b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/main.go new file mode 100644 index 00000000..c3fa1584 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/FunctionInputsAndOutputs/main.go @@ -0,0 +1,58 @@ +package main + +import "fmt" + +func op(op string, x int, y int) int { + if op == "+" { + return x + y + } else { + return x - y + } +} + +func op2(op string, x int, y int) (res int) { + if op == "+" { + res = x + y + } else { + return x - y + } + return +} + +type counter struct { + count int +} + +func (c *counter) bump() int { + c.count++ + return c.count +} + +func test() (int, int) { + return 23, 42 +} + +func test2() (x int, y int) { + y, x = 42, 23 + return +} + +func test3(b bool) (int, y int) { + defer func() { + y++ + }() + if b { + return 0, 1 + } + return 0, 4 +} + +func main() { + op("+", 1, 1) + c := counter{} + op2("-", 2, c.bump()) + x, y := test() + fmt.Printf("%d, %d", x, y) + x, y = test2() + fmt.Printf("%d, %d", x, y) +} diff --git a/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/GlobalValueNumber.expected b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/GlobalValueNumber.expected new file mode 100644 index 00000000..cf57f720 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/GlobalValueNumber.expected @@ -0,0 +1,42 @@ +| main.go:6:2:6:5 | 1 | main.go:14:7:14:7 | 1 | +| main.go:10:2:10:2 | definition of x | main.go:10:7:10:7 | 0 | +| main.go:10:7:10:7 | 0 | main.go:10:7:10:7 | 0 | +| main.go:11:6:11:6 | definition of y | main.go:10:7:10:7 | 0 | +| main.go:11:6:11:6 | zero value for y | main.go:10:7:10:7 | 0 | +| main.go:12:2:12:18 | call to Println | main.go:12:2:12:18 | call to Println | +| main.go:12:14:12:14 | x | main.go:10:7:10:7 | 0 | +| main.go:12:17:12:17 | y | main.go:10:7:10:7 | 0 | +| main.go:14:2:14:2 | definition of z | main.go:14:7:14:7 | 1 | +| main.go:14:7:14:7 | 1 | main.go:14:7:14:7 | 1 | +| main.go:15:2:15:9 | call to bump | main.go:15:2:15:9 | call to bump | +| main.go:16:2:16:21 | call to Println | main.go:16:2:16:21 | call to Println | +| main.go:16:14:16:14 | x | main.go:10:7:10:7 | 0 | +| main.go:16:17:16:17 | y | main.go:10:7:10:7 | 0 | +| main.go:18:2:18:3 | definition of ss | main.go:18:8:18:24 | call to make | +| main.go:18:8:18:24 | call to make | main.go:18:8:18:24 | call to make | +| main.go:18:23:18:23 | 3 | main.go:18:23:18:23 | 3 | +| main.go:19:5:19:5 | 2 | main.go:19:5:19:5 | 2 | +| main.go:19:10:19:24 | "Hello, world!" | main.go:19:10:19:24 | "Hello, world!" | +| main.go:20:2:20:16 | call to Println | main.go:20:2:20:16 | call to Println | +| main.go:23:14:23:16 | implicit read of res | main.go:24:8:24:8 | 4 | +| main.go:23:14:23:16 | zero value for res | main.go:10:7:10:7 | 0 | +| main.go:24:2:24:4 | definition of res | main.go:24:8:24:8 | 4 | +| main.go:24:8:24:8 | 4 | main.go:24:8:24:8 | 4 | +| main.go:28:15:28:17 | implicit read of res | main.go:30:9:30:9 | 6 | +| main.go:28:15:28:17 | zero value for res | main.go:10:7:10:7 | 0 | +| main.go:29:8:29:8 | 5 | main.go:29:8:29:8 | 5 | +| main.go:30:9:30:9 | 6 | main.go:30:9:30:9 | 6 | +| main.go:30:9:30:9 | definition of res | main.go:30:9:30:9 | 6 | +| main.go:33:15:33:17 | definition of res | main.go:10:7:10:7 | 0 | +| main.go:33:15:33:17 | zero value for res | main.go:10:7:10:7 | 0 | +| main.go:34:2:34:4 | definition of res | main.go:34:8:34:8 | 7 | +| main.go:34:8:34:8 | 7 | main.go:34:8:34:8 | 7 | +| main.go:35:8:37:4 | function call | main.go:35:8:37:4 | function call | +| main.go:36:3:36:5 | definition of res | main.go:36:9:36:9 | 8 | +| main.go:36:9:36:9 | 8 | main.go:36:9:36:9 | 8 | +| main.go:38:9:38:9 | 9 | main.go:38:9:38:9 | 9 | +| main.go:38:9:38:9 | definition of res | main.go:38:9:38:9 | 9 | +| regressions.go:5:11:5:31 | call to Sizeof | regressions.go:5:11:5:31 | call to Sizeof | +| regressions.go:7:11:7:15 | false | regressions.go:7:11:7:15 | false | +| regressions.go:9:11:9:12 | !... | regressions.go:11:11:11:14 | true | +| regressions.go:11:11:11:14 | true | regressions.go:11:11:11:14 | true | diff --git a/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/GlobalValueNumber.ql b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/GlobalValueNumber.ql new file mode 100644 index 00000000..91704969 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/GlobalValueNumber.ql @@ -0,0 +1,13 @@ +import go + +from DataFlow::Node nd, Expr init +where + ( + init instanceof BasicLit or + init instanceof CompositeLit or + init instanceof CallExpr or + init = Builtin::true_().getAReference() or + init = Builtin::false_().getAReference() + ) and + globalValueNumber(nd) = init.getGlobalValueNumber() +select nd, init diff --git a/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/UniqueGlobalValueNumber.expected b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/UniqueGlobalValueNumber.expected new file mode 100644 index 00000000..e69de29b diff --git a/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/UniqueGlobalValueNumber.ql b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/UniqueGlobalValueNumber.ql new file mode 100644 index 00000000..05dac05a --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/UniqueGlobalValueNumber.ql @@ -0,0 +1,7 @@ +import go + +// This test should not have any results + +from DataFlow::Node nd, int n +where n = count(globalValueNumber(nd)) and n != 1 +select nd, n diff --git a/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/main.go b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/main.go new file mode 100644 index 00000000..85d28aa4 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/main.go @@ -0,0 +1,39 @@ +package main + +import "fmt" + +func bump(x *int) { + *x++ +} + +func main() { + x := 0 + var y int + fmt.Println(x, y) + + z := 1 + bump(&z) + fmt.Println(x, y, z) + + ss := make([]string, 3) + ss[2] = "Hello, world!" + fmt.Println(ss) +} + +func test() (res int) { + res = 4 + return +} + +func test2() (res int) { + res = 5 + return 6 +} + +func test3() (res int) { + res = 7 + defer func() { + res = 8 + }() + return 9 +} diff --git a/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/regressions.go b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/regressions.go new file mode 100644 index 00000000..0388d289 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/GlobalValueNumbering/regressions.go @@ -0,0 +1,11 @@ +package main + +import "unsafe" + +const c = unsafe.Sizeof(test()) + +const d = false + +const e = !d + +const f = true diff --git a/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.expected b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.expected new file mode 100644 index 00000000..a670304f --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.expected @@ -0,0 +1,18 @@ +| destructuring.go:6:13:6:21 | "tainted" | destructuring.go:20:11:20:12 | s1 | +| destructuring.go:6:13:6:21 | "tainted" | destructuring.go:27:11:27:12 | s1 | +| destructuring.go:11:13:11:26 | "also tainted" | destructuring.go:34:11:34:12 | s3 | +| dispatch.go:32:13:32:21 | "source7" | dispatch.go:14:11:14:13 | arg | +| dispatch.go:32:13:32:21 | "source7" | dispatch.go:19:11:19:13 | arg | +| fields.go:10:13:10:21 | "source5" | fields.go:29:11:29:13 | selection of f | +| fields.go:10:13:10:21 | "source5" | main.go:10:14:10:14 | x | +| fields.go:15:14:15:23 | "source5a" | fields.go:29:11:29:13 | selection of f | +| fields.go:15:14:15:23 | "source5a" | main.go:10:14:10:14 | x | +| main.go:15:16:15:24 | "source1" | main.go:10:14:10:14 | x | +| main.go:15:16:15:24 | "source1" | main.go:20:14:20:14 | x | +| main.go:15:16:15:24 | "source1" | main.go:25:11:25:21 | call to id | +| main.go:15:16:15:24 | "source1" | pointers.go:24:14:24:14 | x | +| main.go:39:17:39:25 | "source4" | main.go:20:14:20:14 | x | +| main.go:46:16:46:24 | "source2" | main.go:10:14:10:14 | x | +| main.go:53:16:53:24 | "source3" | main.go:10:14:10:14 | x | +| pointers.go:6:13:6:21 | "source6" | main.go:10:14:10:14 | x | +| pointers.go:6:13:6:21 | "source6" | pointers.go:15:11:15:12 | star expression | diff --git a/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.ql b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.ql new file mode 100644 index 00000000..b98fc915 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/Test.ql @@ -0,0 +1,23 @@ +import go + +class MyConfiguration extends DataFlow::Configuration { + MyConfiguration() { this = "MyConfiguration" } + + override predicate isSource(DataFlow::Node nd) { + exists(ValueEntity v, Write w | + v.getName().matches("source%") and + w.writes(v, nd) + ) + } + + override predicate isSink(DataFlow::Node nd) { + exists(ValueEntity v, Write w | + v.getName().matches("sink%") and + w.writes(v, nd) + ) + } +} + +from MyConfiguration cfg, DataFlow::Node source, DataFlow::Node sink +where cfg.hasFlow(source, sink) +select source, sink diff --git a/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/destructuring.go b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/destructuring.go new file mode 100644 index 00000000..f230cbe8 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/destructuring.go @@ -0,0 +1,37 @@ +package main + +import "fmt" + +func test17() (string, string) { + source1 := "tainted" + return source1, "not tainted" +} + +func test17b() (string, string) { + source2 := "also tainted" + return source2, "also not tainted" +} + +func test17c() (string, string) { + return test17b() +} + +func test18(s1, s2 string) { + sink1 := s1 // source1 flows here + sink2 := s2 + fmt.Println(sink1, sink2) +} + +func test19() { + s1, s2 := test17() + sink1 := s1 // source1 flows here + sink2 := s2 + fmt.Println(sink1, sink2) + + test18(test17()) + + s3, s4 := test17c() + sink3 := s3 // source2 flows here + sink4 := s4 + fmt.Println(sink3, sink4) +} diff --git a/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/dispatch.go b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/dispatch.go new file mode 100644 index 00000000..a91a3430 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/dispatch.go @@ -0,0 +1,35 @@ +package main + +import "fmt" + +type t interface { + m(arg string) + n(arg string) +} + +// implements t +type strct struct{} + +func (strct) m(arg string) { + sink6 := arg + fmt.Println(sink6) +} + +func (*strct) n(arg string) { + sink7 := arg + fmt.Println(sink7) +} + +// does not implement t +type strct2 struct{} + +func (strct2) m(arg string) { + sink8 := arg + fmt.Println(sink8) +} + +func test16(arg t) { + source7 := "source7" + arg.m(source7) + arg.n(source7) +} diff --git a/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/fields.go b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/fields.go new file mode 100644 index 00000000..f0099c7d --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/fields.go @@ -0,0 +1,36 @@ +package main + +import "fmt" + +type s struct { + f, g string +} + +func test8() s { + source5 := "source5" + return s{f: source5, g: "not a source"} +} + +func test8a() s { + source5a := "source5a" + var s s + s.f = source5a + return s +} + +func test9() { + test1(test8().f) + test1(test8().g) + test1(test8a().f) + test1(test8a().g) +} + +func test10(x s) { + sink4 := x.f + fmt.Println(sink4) +} + +func test11() { + test10(test8()) + test10(test8a()) +} diff --git a/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/main.go b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/main.go new file mode 100644 index 00000000..6f855ab0 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/main.go @@ -0,0 +1,56 @@ +package main + +import "fmt" + +func id(x string) string { + return x +} + +func test1(x string) { + var sink1 = x + fmt.Println(sink1) +} + +func test2() string { + var source1 = "source1" + return source1 +} + +func test3(x string) { + var sink2 = x + fmt.Println(sink2) +} + +func test4() { + sink3 := id(test2()) + test3(sink3) +} + +var v string + +func test5(x string) { + v = x +} + +func test6() { + test1(v) +} + +const source4 = "source4" + +func test7() { + test3(source4) // flow through constants +} + +func main() { + var source2 = "source2" + test1(source2) // flow into function + test1(test2()) // flow out of, and then into function + id(source2) + test1(id("not a source")) // no flow + + // flow through package variables + var source3 = "source3" + test5(source3) + test6() +} diff --git a/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/pointers.go b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/pointers.go new file mode 100644 index 00000000..9b2fbbe1 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/InterProceduralDataFlow/pointers.go @@ -0,0 +1,31 @@ +package main + +import "fmt" + +func test12() *string { + source6 := "source6" + return &source6 +} + +func test13() { + test1(*test12()) +} + +func test14(x *string) { + sink5 := *x + fmt.Println(sink5) +} + +func test15() { + test14(test12()) +} + +func test3a(x string) *string { + var sink2 = x + fmt.Println(sink2) + return &x +} + +func test4a() { + test3a(test2()) +} diff --git a/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.expected b/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.expected new file mode 100644 index 00000000..dbc7327f --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.expected @@ -0,0 +1,3 @@ +| main.go:7:14:7:24 | ...+... | + | main.go:7:14:7:14 | x | main.go:7:19:7:23 | ...+... | +| main.go:7:19:7:23 | ...+... | + | main.go:7:19:7:19 | y | main.go:7:23:7:23 | z | +| main.go:15:2:15:13 | ... += ... | + | main.go:15:2:15:6 | index expression | main.go:15:11:15:13 | "!" | diff --git a/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.ql b/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.ql new file mode 100644 index 00000000..89d115d8 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Nodes/BinaryOperationNodes.ql @@ -0,0 +1,4 @@ +import go + +from DataFlow::BinaryOperationNode binop +select binop, binop.getOperator(), binop.getLeftOperand(), binop.getRightOperand() diff --git a/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode.expected b/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode.expected new file mode 100644 index 00000000..5a3836d1 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode.expected @@ -0,0 +1,4 @@ +| main.go:7:2:7:25 | call to Println | +| main.go:8:5:8:7 | call to f | +| main.go:12:8:12:24 | call to make | +| main.go:14:2:14:26 | call to Println | diff --git a/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode.ql b/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode.ql new file mode 100644 index 00000000..e34e5d1c --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode.ql @@ -0,0 +1,4 @@ +import go + +from DataFlow::CallNode c +select c diff --git a/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode_getArgument.expected b/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode_getArgument.expected new file mode 100644 index 00000000..9d2c1e18 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode_getArgument.expected @@ -0,0 +1,5 @@ +| main.go:7:2:7:25 | call to Println | 0 | main.go:7:14:7:24 | ...+... | +| main.go:12:8:12:24 | call to make | 0 | main.go:12:23:12:23 | 1 | +| main.go:14:2:14:26 | call to Println | 0 | main.go:14:14:14:15 | ss | +| main.go:14:2:14:26 | call to Println | 1 | main.go:14:18:14:18 | 0 | +| main.go:14:2:14:26 | call to Println | 2 | main.go:14:21:14:25 | index expression | diff --git a/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode_getArgument.ql b/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode_getArgument.ql new file mode 100644 index 00000000..a59b32d4 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Nodes/CallNode_getArgument.ql @@ -0,0 +1,4 @@ +import go + +from DataFlow::CallNode c, int i +select c, i, c.getArgument(i) diff --git a/ql/test/library-tests/semmle/go/dataflow/Nodes/main.go b/ql/test/library-tests/semmle/go/dataflow/Nodes/main.go new file mode 100644 index 00000000..f576f2cd --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Nodes/main.go @@ -0,0 +1,16 @@ +package main + +import "fmt" + +func main() { + x, y, z := 1, 2, 3 + fmt.Println(x + (y + z)) + go f() +} + +func f() { + ss := make([]string, 1) + ss[0] = "hi" + fmt.Println(ss, 0, ss[0]) + ss[0] += "!" +} diff --git a/ql/test/library-tests/semmle/go/dataflow/Properties/Property_checkOn.expected b/ql/test/library-tests/semmle/go/dataflow/Properties/Property_checkOn.expected new file mode 100644 index 00000000..f155c472 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Properties/Property_checkOn.expected @@ -0,0 +1,40 @@ +| is false | main.go:6:14:6:14 | b | false | main.go:6:14:6:14 | b | +| is false | main.go:6:14:6:22 | ...==... | false | main.go:6:14:6:14 | b | +| is false | main.go:6:14:6:22 | ...==... | false | main.go:6:14:6:22 | ...==... | +| is false | main.go:6:19:6:22 | true | false | main.go:6:19:6:22 | true | +| is false | main.go:7:14:7:18 | false | false | main.go:7:14:7:18 | false | +| is false | main.go:7:14:7:23 | ...==... | false | main.go:7:14:7:23 | ...==... | +| is false | main.go:7:14:7:23 | ...==... | true | main.go:7:23:7:23 | b | +| is false | main.go:7:23:7:23 | b | false | main.go:7:23:7:23 | b | +| is false | main.go:8:14:8:14 | b | false | main.go:8:14:8:14 | b | +| is false | main.go:8:14:8:22 | ...!=... | false | main.go:8:14:8:22 | ...!=... | +| is false | main.go:8:14:8:22 | ...!=... | true | main.go:8:14:8:14 | b | +| is false | main.go:8:19:8:22 | true | false | main.go:8:19:8:22 | true | +| is false | main.go:9:14:9:14 | b | false | main.go:9:14:9:14 | b | +| is false | main.go:9:14:9:23 | ...!=... | false | main.go:9:14:9:14 | b | +| is false | main.go:9:14:9:23 | ...!=... | false | main.go:9:14:9:23 | ...!=... | +| is false | main.go:9:19:9:23 | false | false | main.go:9:19:9:23 | false | +| is false | main.go:13:14:13:21 | ...==... | false | main.go:13:14:13:21 | ...==... | +| is false | main.go:14:14:14:21 | ...!=... | false | main.go:14:14:14:21 | ...!=... | +| is nil | main.go:13:14:13:21 | ...==... | true | main.go:13:14:13:14 | s | +| is nil | main.go:14:14:14:21 | ...!=... | false | main.go:14:14:14:14 | s | +| is not nil | main.go:13:14:13:21 | ...==... | false | main.go:13:14:13:14 | s | +| is not nil | main.go:14:14:14:21 | ...!=... | true | main.go:14:14:14:14 | s | +| is true | main.go:6:14:6:14 | b | true | main.go:6:14:6:14 | b | +| is true | main.go:6:14:6:22 | ...==... | true | main.go:6:14:6:14 | b | +| is true | main.go:6:14:6:22 | ...==... | true | main.go:6:14:6:22 | ...==... | +| is true | main.go:6:19:6:22 | true | true | main.go:6:19:6:22 | true | +| is true | main.go:7:14:7:18 | false | true | main.go:7:14:7:18 | false | +| is true | main.go:7:14:7:23 | ...==... | false | main.go:7:23:7:23 | b | +| is true | main.go:7:14:7:23 | ...==... | true | main.go:7:14:7:23 | ...==... | +| is true | main.go:7:23:7:23 | b | true | main.go:7:23:7:23 | b | +| is true | main.go:8:14:8:14 | b | true | main.go:8:14:8:14 | b | +| is true | main.go:8:14:8:22 | ...!=... | false | main.go:8:14:8:14 | b | +| is true | main.go:8:14:8:22 | ...!=... | true | main.go:8:14:8:22 | ...!=... | +| is true | main.go:8:19:8:22 | true | true | main.go:8:19:8:22 | true | +| is true | main.go:9:14:9:14 | b | true | main.go:9:14:9:14 | b | +| is true | main.go:9:14:9:23 | ...!=... | true | main.go:9:14:9:14 | b | +| is true | main.go:9:14:9:23 | ...!=... | true | main.go:9:14:9:23 | ...!=... | +| is true | main.go:9:19:9:23 | false | true | main.go:9:19:9:23 | false | +| is true | main.go:13:14:13:21 | ...==... | true | main.go:13:14:13:21 | ...==... | +| is true | main.go:14:14:14:21 | ...!=... | true | main.go:14:14:14:21 | ...!=... | diff --git a/ql/test/library-tests/semmle/go/dataflow/Properties/Property_checkOn.ql b/ql/test/library-tests/semmle/go/dataflow/Properties/Property_checkOn.ql new file mode 100644 index 00000000..5ddf8f28 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Properties/Property_checkOn.ql @@ -0,0 +1,5 @@ +import go + +from DataFlow::Property prop, DataFlow::Node test, boolean outcome, DataFlow::Node nd +where prop.checkOn(test, outcome, nd) +select prop, test, outcome, nd diff --git a/ql/test/library-tests/semmle/go/dataflow/Properties/main.go b/ql/test/library-tests/semmle/go/dataflow/Properties/main.go new file mode 100644 index 00000000..2d4b1abc --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/Properties/main.go @@ -0,0 +1,17 @@ +package main + +import "fmt" + +func test(b bool) { + fmt.Println(b == true) + fmt.Println(false == b) + fmt.Println(b != true) + fmt.Println(b != false) +} + +func test2(s interface{}) { + fmt.Println(s == nil) + fmt.Println(s != nil) +} + +func main() {} diff --git a/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.expected b/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.expected new file mode 100644 index 00000000..26b87ce1 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.expected @@ -0,0 +1,30 @@ +| main.go:15:12:15:12 | x | main.go:13:6:13:6 | definition of x | x | +| main.go:15:15:15:15 | y | main.go:14:2:14:2 | definition of y | y | +| main.go:17:3:17:3 | y | main.go:14:2:14:2 | definition of y | y | +| main.go:19:12:19:12 | x | main.go:13:6:13:6 | definition of x | x | +| main.go:19:15:19:15 | y | main.go:19:2:19:2 | y = phi(def@14:2, def@17:3) | y | +| main.go:21:7:21:7 | y | main.go:19:2:19:2 | y = phi(def@14:2, def@17:3) | y | +| main.go:23:12:23:12 | x | main.go:23:2:23:2 | x = phi(def@13:6, def@21:3) | x | +| main.go:23:15:23:15 | y | main.go:19:2:19:2 | y = phi(def@14:2, def@17:3) | y | +| main.go:27:10:27:10 | x | main.go:26:10:26:10 | definition of x | x | +| main.go:29:10:29:10 | b | main.go:27:5:27:5 | definition of b | b | +| main.go:29:13:29:13 | a | main.go:27:2:27:2 | definition of a | a | +| main.go:31:9:31:9 | a | main.go:31:9:31:9 | a = phi(def@27:2, def@29:3) | a | +| main.go:31:12:31:12 | b | main.go:31:9:31:9 | b = phi(def@27:5, def@29:6) | b | +| main.go:35:3:35:3 | x | main.go:34:11:34:11 | definition of x | x | +| main.go:40:10:40:10 | x | main.go:39:2:39:2 | definition of x | x | +| main.go:42:8:42:10 | ptr | main.go:40:2:40:4 | definition of ptr | ptr | +| main.go:44:12:44:12 | x | main.go:39:2:39:2 | definition of x | x | +| main.go:47:13:47:18 | implicit read of result | main.go:48:2:48:7 | definition of result | result | +| main.go:52:14:52:19 | implicit read of result | main.go:52:14:52:19 | definition of result | result | +| main.go:61:12:61:12 | x | main.go:58:6:58:6 | x = phi(def@57:6, def@59:3) | x | +| main.go:64:16:64:16 | i | main.go:65:6:65:6 | i = phi(def@64:16, def@64:6) | i | +| main.go:70:12:70:12 | y | main.go:65:6:65:6 | y = phi(def@63:2, def@68:3) | y | +| main.go:73:16:73:16 | i | main.go:74:3:74:3 | i = phi(def@73:16, def@73:6) | i | +| main.go:79:12:79:12 | z | main.go:74:3:74:3 | definition of z | z | +| main.go:82:18:82:18 | implicit read of a | main.go:84:5:84:5 | definition of a | a | +| main.go:82:25:82:25 | implicit read of b | main.go:82:25:82:25 | definition of b | b | +| main.go:84:9:84:9 | x | main.go:83:2:83:2 | definition of x | x | +| main.go:84:15:84:15 | x | main.go:83:2:83:2 | definition of x | x | +| main.go:94:2:94:8 | wrapper | main.go:92:22:92:28 | definition of wrapper | wrapper | +| main.go:97:9:97:9 | x | main.go:94:2:96:3 | capture variable x | x | diff --git a/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.ql b/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.ql new file mode 100644 index 00000000..1ebb0c5b --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/SSA/DefUse.ql @@ -0,0 +1,4 @@ +import go + +from SsaVariable ssa +select ssa.getAUse(), ssa.getDefinition(), ssa.getSourceVariable() diff --git a/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.expected b/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.expected new file mode 100644 index 00000000..faca455b --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.expected @@ -0,0 +1,38 @@ +| main.go:13:6:13:6 | definition of x | +| main.go:14:2:14:2 | definition of y | +| main.go:17:3:17:3 | definition of y | +| main.go:19:2:19:2 | y = phi(def@14:2, def@17:3) | +| main.go:21:3:21:3 | definition of x | +| main.go:23:2:23:2 | x = phi(def@13:6, def@21:3) | +| main.go:26:10:26:10 | definition of x | +| main.go:27:2:27:2 | definition of a | +| main.go:27:5:27:5 | definition of b | +| main.go:29:3:29:3 | definition of a | +| main.go:29:6:29:6 | definition of b | +| main.go:31:9:31:9 | a = phi(def@27:2, def@29:3) | +| main.go:31:9:31:9 | b = phi(def@27:5, def@29:6) | +| main.go:34:11:34:11 | definition of x | +| main.go:39:2:39:2 | definition of x | +| main.go:40:2:40:4 | definition of ptr | +| main.go:48:2:48:7 | definition of result | +| main.go:52:14:52:19 | definition of result | +| main.go:57:6:57:6 | definition of x | +| main.go:58:6:58:6 | x = phi(def@57:6, def@59:3) | +| main.go:59:3:59:3 | definition of x | +| main.go:63:2:63:2 | definition of y | +| main.go:64:6:64:6 | definition of i | +| main.go:64:16:64:18 | definition of i | +| main.go:65:6:65:6 | i = phi(def@64:16, def@64:6) | +| main.go:65:6:65:6 | y = phi(def@63:2, def@68:3) | +| main.go:68:3:68:3 | definition of y | +| main.go:73:6:73:6 | definition of i | +| main.go:73:16:73:18 | definition of i | +| main.go:74:3:74:3 | definition of z | +| main.go:74:3:74:3 | i = phi(def@73:16, def@73:6) | +| main.go:82:25:82:25 | definition of b | +| main.go:83:2:83:2 | definition of x | +| main.go:84:5:84:5 | definition of a | +| main.go:92:22:92:28 | definition of wrapper | +| main.go:93:2:93:2 | definition of x | +| main.go:94:2:96:3 | capture variable x | +| main.go:95:3:95:3 | definition of x | diff --git a/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.ql b/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.ql new file mode 100644 index 00000000..e9ca0b31 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/SSA/SsaDefinition.ql @@ -0,0 +1,4 @@ +import go + +from SsaDefinition ssa +select ssa diff --git a/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.expected b/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.expected new file mode 100644 index 00000000..c951607d --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.expected @@ -0,0 +1,34 @@ +| main.go:13:6:13:6 | assignment to x | x | main.go:13:6:13:6 | zero value for x | +| main.go:14:2:14:2 | assignment to y | y | main.go:14:7:14:8 | 23 | +| main.go:17:3:17:3 | assignment to y | y | main.go:17:3:17:9 | ... += ... | +| main.go:21:3:21:3 | assignment to x | x | main.go:21:7:21:7 | y | +| main.go:26:10:26:10 | initialization of x | x | main.go:26:10:26:10 | argument corresponding to x | +| main.go:27:2:27:2 | assignment to a | a | main.go:27:10:27:10 | x | +| main.go:27:5:27:5 | assignment to b | b | main.go:27:13:27:13 | 0 | +| main.go:29:3:29:3 | assignment to a | a | main.go:29:10:29:10 | b | +| main.go:29:6:29:6 | assignment to b | b | main.go:29:13:29:13 | a | +| main.go:34:11:34:11 | initialization of x | x | main.go:34:11:34:11 | argument corresponding to x | +| main.go:39:2:39:2 | assignment to x | x | main.go:39:7:39:8 | 23 | +| main.go:40:2:40:4 | assignment to ptr | ptr | main.go:40:9:40:10 | &... | +| main.go:47:13:47:18 | initialization of result | result | main.go:47:13:47:18 | zero value for result | +| main.go:48:2:48:7 | assignment to result | result | main.go:48:11:48:12 | 42 | +| main.go:52:14:52:19 | initialization of result | result | main.go:52:14:52:19 | zero value for result | +| main.go:57:6:57:6 | assignment to x | x | main.go:57:6:57:6 | zero value for x | +| main.go:59:3:59:3 | assignment to x | x | main.go:59:7:59:7 | 2 | +| main.go:63:2:63:2 | assignment to y | y | main.go:63:7:63:7 | 1 | +| main.go:64:6:64:6 | assignment to i | i | main.go:64:11:64:11 | 0 | +| main.go:64:16:64:18 | increment statement | i | main.go:64:16:64:18 | rhs of increment statement | +| main.go:68:3:68:3 | assignment to y | y | main.go:68:7:68:7 | 2 | +| main.go:72:2:72:2 | assignment to z | z | main.go:72:7:72:7 | 1 | +| main.go:73:6:73:6 | assignment to i | i | main.go:73:11:73:11 | 0 | +| main.go:73:16:73:18 | increment statement | i | main.go:73:16:73:18 | rhs of increment statement | +| main.go:74:3:74:3 | assignment to z | z | main.go:74:7:74:7 | 2 | +| main.go:82:18:82:18 | initialization of a | a | main.go:82:18:82:18 | zero value for a | +| main.go:82:25:82:25 | initialization of b | b | main.go:82:25:82:25 | zero value for b | +| main.go:83:2:83:2 | assignment to x | x | main.go:83:7:83:8 | 23 | +| main.go:84:2:84:2 | assignment to x | x | main.go:84:9:84:12 | ...+... | +| main.go:84:5:84:5 | assignment to a | a | main.go:84:15:84:15 | x | +| main.go:90:15:90:16 | initialization of cb | cb | main.go:90:15:90:16 | argument corresponding to cb | +| main.go:92:22:92:28 | initialization of wrapper | wrapper | main.go:92:22:92:28 | argument corresponding to wrapper | +| main.go:93:2:93:2 | assignment to x | x | main.go:93:7:93:7 | 0 | +| main.go:95:3:95:3 | assignment to x | x | main.go:95:7:95:7 | 1 | diff --git a/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.ql b/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.ql new file mode 100644 index 00000000..7e4a9d45 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/SSA/VarDefs.ql @@ -0,0 +1,5 @@ +import go + +from IR::Instruction d, Variable v, ControlFlow::Node rhs +where d.writes(v, rhs) +select d, v, rhs diff --git a/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.expected b/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.expected new file mode 100644 index 00000000..b6cd184c --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.expected @@ -0,0 +1,30 @@ +| main.go:15:12:15:12 | x | x | +| main.go:15:15:15:15 | y | y | +| main.go:17:3:17:3 | y | y | +| main.go:19:12:19:12 | x | x | +| main.go:19:15:19:15 | y | y | +| main.go:21:7:21:7 | y | y | +| main.go:23:12:23:12 | x | x | +| main.go:23:15:23:15 | y | y | +| main.go:27:10:27:10 | x | x | +| main.go:29:10:29:10 | b | b | +| main.go:29:13:29:13 | a | a | +| main.go:31:9:31:9 | a | a | +| main.go:31:12:31:12 | b | b | +| main.go:35:3:35:3 | x | x | +| main.go:40:10:40:10 | x | x | +| main.go:42:8:42:10 | ptr | ptr | +| main.go:44:12:44:12 | x | x | +| main.go:47:13:47:18 | implicit read of result | result | +| main.go:52:14:52:19 | implicit read of result | result | +| main.go:61:12:61:12 | x | x | +| main.go:64:16:64:16 | i | i | +| main.go:70:12:70:12 | y | y | +| main.go:73:16:73:16 | i | i | +| main.go:79:12:79:12 | z | z | +| main.go:82:18:82:18 | implicit read of a | a | +| main.go:82:25:82:25 | implicit read of b | b | +| main.go:84:9:84:9 | x | x | +| main.go:84:15:84:15 | x | x | +| main.go:94:2:94:8 | wrapper | wrapper | +| main.go:97:9:97:9 | x | x | diff --git a/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.ql b/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.ql new file mode 100644 index 00000000..918ca96d --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/SSA/VarUses.ql @@ -0,0 +1,5 @@ +import go + +from IR::Instruction u, Variable v +where u.reads(v) +select u, v diff --git a/ql/test/library-tests/semmle/go/dataflow/SSA/main.go b/ql/test/library-tests/semmle/go/dataflow/SSA/main.go new file mode 100644 index 00000000..8c20bb6d --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/SSA/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "math/rand" +) + +func cond() bool { + return rand.Float64() >= 0.5 +} + +func main() { + var x int + y := 23 + fmt.Print(x, y) + if cond() { + y += 19 + } + fmt.Print(x, y) + if cond() { + x = y + } + fmt.Print(x, y) +} + +func foo(x int) (int, int) { + a, b := x, 0 + if cond() { + a, b = b, a + } + return a, b +} + +func bump(x *int) { + *x += 19 +} + +func bar() { + x := 23 + ptr := &x + if cond() { + bump(ptr) + } + fmt.Print(x) +} + +func baz() (result int) { + result = 42 + return +} + +func baz2() (result int) { + return +} + +func loops() { + var x int + for cond() { + x = 2 + } + fmt.Print(x) + + y := 1 + for i := 0; ; i++ { + if cond() { + break + } + y = 2 + } + fmt.Print(y) + + z := 1 + for i := 0; ; i++ { + z = 2 + if cond() { + break + } + } + fmt.Print(z) +} + +func multiRes() (a int, b float32) { + x := 23 + x, a = x+19, x + return +} + +type s struct{} + +func (*s) foo(cb func()) {} + +func updateInClosure(wrapper struct{ s }) int { + x := 0 + wrapper.s.foo(func() { + x = 1 + }) + return x +} diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Header.expected b/ql/test/library-tests/semmle/go/frameworks/HTTP/Header.expected new file mode 100644 index 00000000..62a0488c --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Header.expected @@ -0,0 +1,10 @@ +| main.go:29:2:29:19 | call to WriteHeader | n/a | 418 | status | 418 | +| main.go:31:2:31:51 | call to Set | "Authorization" | "Basic example:example" | authorization | Basic example:example | +| main.go:32:2:32:26 | call to Add | "Age" | "342232" | age | 342232 | +| main.go:34:2:34:55 | call to Add | server | call to Sprintf | n/a | n/a | +| main.go:35:2:35:36 | call to Set | LOC_HEADER | ...+... | n/a | n/a | +| main.go:36:2:36:5 | head | "Unknown-Header" | composite literal | n/a | n/a | +| main.go:48:2:48:43 | call to Add | "Not-A-Response" | "Header" | not-a-response | Header | +| main.go:49:2:49:42 | call to Set | "Accept" | "nota/response" | accept | nota/response | +| main.go:50:2:50:11 | selection of Header | "Accept-Charset" | composite literal | n/a | n/a | +| main.go:57:2:57:42 | call to Set | "This-Makes" | "No sense" | this-makes | No sense | diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/Header.ql b/ql/test/library-tests/semmle/go/frameworks/HTTP/Header.ql new file mode 100644 index 00000000..e5449bf3 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/Header.ql @@ -0,0 +1,22 @@ +import go + +from HTTP::HeaderWrite headerWrite, string name, string value, string definedName, string definedVal +where + ( + name = headerWrite.getName().toString() + or + not exists(headerWrite.getName()) and name = "n/a" + ) and + ( + value = headerWrite.getValue().toString() + or + not exists(headerWrite.getValue()) and value = "n/a" + ) and + ( + headerWrite.definesHeader(definedName, definedVal) + or + not headerWrite.definesHeader(_, _) and + definedName = "n/a" and + definedVal = "n/a" + ) +select headerWrite, name, value, definedName, definedVal diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/RequestBody.expected b/ql/test/library-tests/semmle/go/frameworks/HTTP/RequestBody.expected new file mode 100644 index 00000000..1d2ac44d --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/RequestBody.expected @@ -0,0 +1,2 @@ +| main.go:46:57:46:59 | nil | +| main.go:52:13:52:34 | call to NopCloser | diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/RequestBody.ql b/ql/test/library-tests/semmle/go/frameworks/HTTP/RequestBody.ql new file mode 100644 index 00000000..d49b7e95 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/RequestBody.ql @@ -0,0 +1,4 @@ +import go + +from HTTP::RequestBody rb +select rb diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/ResponseBody.expected b/ql/test/library-tests/semmle/go/frameworks/HTTP/ResponseBody.expected new file mode 100644 index 00000000..f36de031 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/ResponseBody.expected @@ -0,0 +1,2 @@ +| main.go:38:10:38:40 | type conversion | +| main.go:40:20:40:30 | "Body text" | diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/ResponseBody.ql b/ql/test/library-tests/semmle/go/frameworks/HTTP/ResponseBody.ql new file mode 100644 index 00000000..d8d89b7f --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/ResponseBody.ql @@ -0,0 +1,4 @@ +import go + +from HTTP::ResponseBody rb +select rb diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/UntrustedFlowSources.expected b/ql/test/library-tests/semmle/go/frameworks/HTTP/UntrustedFlowSources.expected new file mode 100644 index 00000000..0ddcc26e --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/UntrustedFlowSources.expected @@ -0,0 +1,11 @@ +| main.go:16:10:16:15 | selection of Body | +| main.go:17:10:17:15 | selection of Form | +| main.go:18:10:18:17 | selection of Header | +| main.go:19:10:19:14 | selection of URL | +| main.go:23:10:23:16 | selection of Body | +| main.go:24:10:24:16 | selection of Form | +| main.go:25:10:25:18 | selection of Header | +| main.go:26:10:26:15 | selection of URL | +| main.go:48:2:48:11 | selection of Header | +| main.go:49:2:49:11 | selection of Header | +| main.go:50:2:50:11 | selection of Header | diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/UntrustedFlowSources.ql b/ql/test/library-tests/semmle/go/frameworks/HTTP/UntrustedFlowSources.ql new file mode 100644 index 00000000..0715d64f --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/UntrustedFlowSources.ql @@ -0,0 +1,3 @@ +import go + +select any(UntrustedFlowSource ufs) diff --git a/ql/test/library-tests/semmle/go/frameworks/HTTP/main.go b/ql/test/library-tests/semmle/go/frameworks/HTTP/main.go new file mode 100644 index 00000000..6a55524d --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/HTTP/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net/http" +) + +const LOC_HEADER = "Location" + +func use(xs ...interface{}) {} + +func handler(w http.ResponseWriter, r *http.Request) { + rfs1 := r.Body + rfs2 := r.Form + rfs3 := r.Header + rfs4 := r.URL + use(rfs1, rfs2, rfs3, rfs4) + + r2 := *r + rfs5 := r2.Body + rfs6 := r2.Form + rfs7 := r2.Header + rfs8 := r2.URL + use(rfs5, rfs6, rfs7, rfs8) + + w.WriteHeader(418) + head := w.Header() + head.Set("Authorization", "Basic example:example") + head.Add("Age", "342232") + server := "Server" + head.Add(server, fmt.Sprintf("Server: %s", "example")) + head.Set(LOC_HEADER, rfs4+"/redir") + head["Unknown-Header"] = []string{"Some value!"} + + w.Write([]byte("Some more body text\n")) + + io.WriteString(w, "Body text") +} + +func main() { + body := bytes.NewBufferString("My request HTTP body") + + req, _ := http.NewRequest("GET", "https://semmle.com", nil) + // These are currently flagged as user controlled + req.Header.Add("Not-A-Response", "Header") + req.Header.Set("Accept", "nota/response") + req.Header["Accept-Charset"] = []string{"utf-8, iso-8859-1;q=0.5"} + + req.Body = ioutil.NopCloser(body) + + http.DefaultClient.Do(req) + + resp, _ := http.Get("https://example.com") + resp.Header.Set("This-Makes", "No sense") +} diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/QueryString.expected b/ql/test/library-tests/semmle/go/frameworks/SQL/QueryString.expected new file mode 100644 index 00000000..c7f37f0c --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/QueryString.expected @@ -0,0 +1,10 @@ +| main.go:11:10:11:14 | query | +| main.go:12:22:12:26 | query | +| main.go:13:13:13:17 | query | +| main.go:14:25:14:29 | query | +| main.go:15:11:15:15 | query | +| main.go:16:23:16:27 | query | +| main.go:17:14:17:18 | query | +| main.go:18:26:18:30 | query | +| main.go:22:57:22:65 | querypart | +| main.go:23:44:23:52 | querypart | diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/QueryString.ql b/ql/test/library-tests/semmle/go/frameworks/SQL/QueryString.ql new file mode 100644 index 00000000..7b56fd97 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/QueryString.ql @@ -0,0 +1,4 @@ +import go + +from SQL::QueryString qs +select qs diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/go.mod b/ql/test/library-tests/semmle/go/frameworks/SQL/go.mod new file mode 100644 index 00000000..529502c1 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/go.mod @@ -0,0 +1,5 @@ +module semmle.go.frameworks.SQL + +go 1.13 + +require github.com/Masterminds/squirrel v1.1.0 diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/main.go b/ql/test/library-tests/semmle/go/frameworks/SQL/main.go new file mode 100644 index 00000000..84b899ea --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "context" + "database/sql" + + "github.com/Masterminds/squirrel" +) + +func test(db *sql.DB, query string, ctx context.Context) { + db.Exec(query) + db.ExecContext(ctx, query) + db.Prepare(query) + db.PrepareContext(ctx, query) + db.Query(query) + db.QueryContext(ctx, query) + db.QueryRow(query) + db.QueryRowContext(ctx, query) +} + +func squirrelTest(querypart string) { + squirrel.Select("*").From("users").Where(squirrel.Expr(querypart)) + squirrel.Select("*").From("users").Suffix(querypart) +} + +func main() {} diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/LICENSE b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/LICENSE new file mode 100644 index 00000000..74c20a2b --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/LICENSE @@ -0,0 +1,23 @@ +Squirrel +The Masterminds +Copyright (C) 2014-2015, Lann Martin +Copyright (C) 2015-2016, Google +Copyright (C) 2015, Matt Farina and Matt Butcher + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/README.md b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/README.md new file mode 100644 index 00000000..d51962ba --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/README.md @@ -0,0 +1,3 @@ +This is a simple stub for https://github.com/Masterminds/squirrel, strictly for use in query testing. + +See the LICENSE file in this folder for information about the licensing of the original library. diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/go.mod b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/go.mod new file mode 100644 index 00000000..48cf5c38 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/go.mod @@ -0,0 +1 @@ +module github.com/Masterminds/squirrel diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/squirrel.go b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/squirrel.go new file mode 100644 index 00000000..5cef56f4 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/github.com/Masterminds/squirrel/squirrel.go @@ -0,0 +1,23 @@ +package squirrel + +type SelectBuilder struct{} + +func Expr(e string) string { + return Expr(e) +} + +func Select(s string) *SelectBuilder { + return &SelectBuilder{} +} + +func (b *SelectBuilder) From(f string) *SelectBuilder { + return b +} + +func (b *SelectBuilder) Where(e string) *SelectBuilder { + return b +} + +func (b *SelectBuilder) Suffix(s string) *SelectBuilder { + return b +} diff --git a/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/modules.txt b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/modules.txt new file mode 100644 index 00000000..1f0e9480 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/SQL/vendor/modules.txt @@ -0,0 +1,2 @@ +# github.com/Masterminds/squirrel v1.1.0 +github.com/Masterminds/squirrel diff --git a/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.expected b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.expected new file mode 100644 index 00000000..561f5381 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.expected @@ -0,0 +1,16 @@ +| crypto.go:9:14:9:31 | call to NewCipher | crypto.go:9:2:9:31 | ... := ...[0] | +| crypto.go:9:14:9:31 | call to NewCipher | crypto.go:9:2:9:31 | ... := ...[1] | +| crypto.go:10:15:10:34 | call to NewGCM | crypto.go:10:2:10:34 | ... := ...[0] | +| crypto.go:10:15:10:34 | call to NewGCM | crypto.go:10:2:10:34 | ... := ...[1] | +| crypto.go:11:18:11:57 | call to Open | crypto.go:11:2:11:57 | ... := ...[0] | +| crypto.go:11:18:11:57 | call to Open | crypto.go:11:2:11:57 | ... := ...[1] | +| crypto.go:11:42:11:51 | ciphertext | crypto.go:11:2:11:57 | ... := ...[0] | +| main.go:10:12:10:26 | call to Marshal | main.go:10:2:10:26 | ... := ...[0] | +| main.go:10:12:10:26 | call to Marshal | main.go:10:2:10:26 | ... := ...[1] | +| main.go:10:25:10:25 | v | main.go:10:2:10:26 | ... := ...[0] | +| main.go:12:14:12:52 | call to MarshalIndent | main.go:12:2:12:52 | ... := ...[0] | +| main.go:12:14:12:52 | call to MarshalIndent | main.go:12:2:12:52 | ... := ...[1] | +| main.go:12:33:12:33 | v | main.go:12:2:12:52 | ... := ...[0] | +| main.go:18:18:18:42 | call to DecodeString | main.go:18:2:18:42 | ... := ...[0] | +| main.go:18:18:18:42 | call to DecodeString | main.go:18:2:18:42 | ... := ...[1] | +| main.go:18:35:18:41 | encoded | main.go:18:2:18:42 | ... := ...[0] | diff --git a/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.ql b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.ql new file mode 100644 index 00000000..2016803c --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.ql @@ -0,0 +1,7 @@ +import go + +from DataFlow::Node pred, DataFlow::Node succ +where + TaintTracking::localTaintStep(pred, succ) and + not DataFlow::localFlowStep(pred, succ) +select pred, succ diff --git a/ql/test/library-tests/semmle/go/frameworks/TaintSteps/crypto.go b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/crypto.go new file mode 100644 index 00000000..0232cc92 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/crypto.go @@ -0,0 +1,13 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" +) + +func cryptoTest(key []byte, nonce []byte, ciphertext []byte) []byte { + block, _ := aes.NewCipher(key) + aesgcm, _ := cipher.NewGCM(block) + plaintext, _ := aesgcm.Open(nil, nonce, ciphertext, nil) + return plaintext +} \ No newline at end of file diff --git a/ql/test/library-tests/semmle/go/frameworks/TaintSteps/main.go b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/main.go new file mode 100644 index 00000000..cd8ffe20 --- /dev/null +++ b/ql/test/library-tests/semmle/go/frameworks/TaintSteps/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "encoding/hex" + "encoding/json" +) + +func jsonTest(v interface{}) []interface{} { + // taint step from v to b + b, err := json.Marshal(v) + // taint step from v to b2 + b2, err2 := json.MarshalIndent(v, "/*JSON*/", " ") + return [](interface{}){b, err, b2, err2} +} + +func hexTest(encoded string) []interface{} { + // taint step from encoded to decoded + decoded, err := hex.DecodeString(encoded) + // no taint step from decoded to encoded: hex-encoded data is not dangerous for injection + // attacks, so until we have support for flow labels we do not track taint through this + reEncoded := hex.EncodeToString(decoded) + return [](interface{}){decoded, err, reEncoded} +} diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/CleartextPasswordExpr.expected b/ql/test/library-tests/semmle/go/security/SensitiveActions/CleartextPasswordExpr.expected new file mode 100644 index 00000000..dd182a4b --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/CleartextPasswordExpr.expected @@ -0,0 +1,12 @@ +| main.go:14:9:14:18 | selection of password | +| main.go:14:11:14:18 | password | +| main.go:32:6:32:13 | password | +| main.go:33:6:33:13 | PassWord | +| main.go:34:6:34:26 | myPasswordInCleartext | +| main.go:37:6:37:15 | selection of password | +| main.go:37:8:37:15 | password | +| main.go:38:2:38:14 | selection of getPassword | +| main.go:38:2:38:16 | call to getPassword | +| main.go:38:4:38:14 | getPassword | +| main.go:39:2:39:18 | call to get | +| main.go:40:2:40:16 | call to get | diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/CleartextPasswordExpr.ql b/ql/test/library-tests/semmle/go/security/SensitiveActions/CleartextPasswordExpr.ql new file mode 100644 index 00000000..96536776 --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/CleartextPasswordExpr.ql @@ -0,0 +1,5 @@ +import go +import semmle.go.security.SensitiveActions + +from CleartextPasswordExpr e +select e diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/DummyPasswords.expected b/ql/test/library-tests/semmle/go/security/SensitiveActions/DummyPasswords.expected new file mode 100644 index 00000000..0da2c1ea --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/DummyPasswords.expected @@ -0,0 +1,9 @@ +| | true | +| XXXXXXXX | true | +| abcdefgh | false | +| admin | true | +| change_me | true | +| example_password | true | +| insert-auth-from-gui | true | +| root | true | +| sOKY6ccizpmvF*32so%Q | false | diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/DummyPasswords.ql b/ql/test/library-tests/semmle/go/security/SensitiveActions/DummyPasswords.ql new file mode 100644 index 00000000..cc881c3b --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/DummyPasswords.ql @@ -0,0 +1,20 @@ +import go +import semmle.go.security.SensitiveActions + +string getASamplePassword() { + result = "abcdefgh" or + result = "sOKY6ccizpmvF*32so%Q" or + result = "XXXXXXXX" or + result = "example_password" or + result = "change_me" or + result = "" or + result = "insert-auth-from-gui" or + result = "admin" or + result = "root" +} + +from string password, boolean isDummy +where + password = getASamplePassword() and + if PasswordHeuristics::isDummyPassword(password) then isDummy = true else isDummy = false +select password, isDummy diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveAction.expected b/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveAction.expected new file mode 100644 index 00000000..428fe165 --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveAction.expected @@ -0,0 +1,2 @@ +| sensitiveactions.go:13:2:13:8 | call to login | +| sensitiveactions.go:14:2:14:7 | call to auth | diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveAction.ql b/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveAction.ql new file mode 100644 index 00000000..af0c4e32 --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveAction.ql @@ -0,0 +1,4 @@ +import go +import semmle.go.security.SensitiveActions + +select any(SensitiveAction a) diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveExpr.expected b/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveExpr.expected new file mode 100644 index 00000000..c0d1ae36 --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveExpr.expected @@ -0,0 +1,13 @@ +| main.go:14:9:14:18 | selection of password | +| main.go:14:11:14:18 | password | +| main.go:32:6:32:13 | password | +| main.go:33:6:33:13 | PassWord | +| main.go:34:6:34:26 | myPasswordInCleartext | +| main.go:37:6:37:15 | selection of password | +| main.go:37:8:37:15 | password | +| main.go:38:2:38:14 | selection of getPassword | +| main.go:38:2:38:16 | call to getPassword | +| main.go:38:4:38:14 | getPassword | +| main.go:39:2:39:18 | call to get | +| main.go:40:2:40:16 | call to get | +| main.go:50:6:50:11 | secret | diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveExpr.ql b/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveExpr.ql new file mode 100644 index 00000000..56316295 --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/SensitiveExpr.ql @@ -0,0 +1,4 @@ +import go +import semmle.go.security.SensitiveActions + +select any(SensitiveExpr a) diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/main.go b/ql/test/library-tests/semmle/go/security/SensitiveActions/main.go new file mode 100644 index 00000000..96f8d44d --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "os" +) + +func use(_ string) {} + +type passStruct struct { + password string +} + +func (p passStruct) getPassword() string { + return p.password +} + +func (p passStruct) get(key string) string { + return "" +} + +func get(key string) string { + return "" +} + +var ( + password, PassWord, myPasswordInCleartext string + hashed_password, password_hashed, password_hash, hash_password, hashedPassword string + secret string +) + +func main() { + use(password) + use(PassWord) + use(myPasswordInCleartext) + + var p passStruct + use(p.password) + p.getPassword() + p.get("password") + get("password") + + use(hashed_password) + use(password_hashed) + use(password_hash) + use(hash_password) + use(hashedPassword) + + os.Exit(0) + + use(secret) +} diff --git a/ql/test/library-tests/semmle/go/security/SensitiveActions/sensitiveactions.go b/ql/test/library-tests/semmle/go/security/SensitiveActions/sensitiveactions.go new file mode 100644 index 00000000..c281f688 --- /dev/null +++ b/ql/test/library-tests/semmle/go/security/SensitiveActions/sensitiveactions.go @@ -0,0 +1,16 @@ +package main + +func loginfo() {} + +func login() {} + +func auth() {} + +func author() {} + +func test() { + loginfo() + login() + auth() + author() +} diff --git a/ql/test/query-tests/AlertSuppression/AlertSuppression.expected b/ql/test/query-tests/AlertSuppression/AlertSuppression.expected new file mode 100644 index 00000000..a2e99357 --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/AlertSuppression.expected @@ -0,0 +1,51 @@ +| AlertSuppressionExample.go:11:42:11:74 | comment | lgtm[go/hardcoded-credentials] | lgtm[go/hardcoded-credentials] | AlertSuppressionExample.go:11:1:11:74 | suppression range | +| tst.go:5:8:5:14 | comment | lgtm | lgtm | tst.go:5:1:5:14 | suppression range | +| tst.go:6:8:6:39 | comment | lgtm[go/redundant-assignment] | lgtm[go/redundant-assignment] | tst.go:6:1:6:39 | suppression range | +| tst.go:7:8:7:39 | comment | lgtm[go/redundant-assignment] | lgtm[go/redundant-assignment] | tst.go:7:1:7:39 | suppression range | +| tst.go:8:8:8:63 | comment | lgtm[go/redundant-assignment, go/redundant-operation] | lgtm[go/redundant-assignment, go/redundant-operation] | tst.go:8:1:8:63 | suppression range | +| tst.go:9:8:9:29 | comment | lgtm[@tag:nullness] | lgtm[@tag:nullness] | tst.go:9:1:9:29 | suppression range | +| tst.go:10:8:10:53 | comment | lgtm[@tag:nullness,go/redundant-assignment] | lgtm[@tag:nullness,go/redundant-assignment] | tst.go:10:1:10:53 | suppression range | +| tst.go:11:8:11:35 | comment | lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | tst.go:11:1:11:35 | suppression range | +| tst.go:12:8:12:70 | comment | lgtm[go/redundant-operation] because I know better than lgtm | lgtm[go/redundant-operation] | tst.go:12:1:12:70 | suppression range | +| tst.go:13:8:13:25 | comment | lgtm: blah blah | lgtm | tst.go:13:1:13:25 | suppression range | +| tst.go:14:8:14:39 | comment | lgtm blah blah #falsepositive | lgtm | tst.go:14:1:14:39 | suppression range | +| tst.go:15:8:15:39 | comment | lgtm [go/redundant-operation] | lgtm [go/redundant-operation] | tst.go:15:1:15:39 | suppression range | +| tst.go:17:8:17:16 | comment | lgtm[] | lgtm[] | tst.go:17:1:17:16 | suppression range | +| tst.go:19:8:19:13 | comment | lgtm | lgtm | tst.go:19:1:19:13 | suppression range | +| tst.go:20:8:20:14 | comment | \tlgtm | lgtm | tst.go:20:1:20:14 | suppression range | +| tst.go:21:8:21:40 | comment | lgtm\t[go/redundant-assignment] | lgtm\t[go/redundant-assignment] | tst.go:21:1:21:40 | suppression range | +| tst.go:24:8:24:19 | comment | foo; lgtm | lgtm | tst.go:24:1:24:19 | suppression range | +| tst.go:25:8:25:44 | comment | foo; lgtm[go/redundant-assignment] | lgtm[go/redundant-assignment] | tst.go:25:1:25:44 | suppression range | +| tst.go:27:8:27:43 | comment | foo lgtm[go/redundant-assignment] | lgtm[go/redundant-assignment] | tst.go:27:1:27:43 | suppression range | +| tst.go:29:8:29:47 | comment | foo lgtm[go/redundant-assignment] bar | lgtm[go/redundant-assignment] | tst.go:29:1:29:47 | suppression range | +| tst.go:30:8:30:15 | comment | LGTM! | LGTM | tst.go:30:1:30:15 | suppression range | +| tst.go:31:8:31:39 | comment | LGTM[go/redundant-assignment] | LGTM[go/redundant-assignment] | tst.go:31:1:31:39 | suppression range | +| tst.go:32:8:32:72 | comment | lgtm[go/redundant-assignment] and lgtm[go/redundant-operation] | lgtm[go/redundant-assignment] | tst.go:32:1:32:72 | suppression range | +| tst.go:32:8:32:72 | comment | lgtm[go/redundant-assignment] and lgtm[go/redundant-operation] | lgtm[go/redundant-operation] | tst.go:32:1:32:72 | suppression range | +| tst.go:33:8:33:45 | comment | lgtm[go/redundant-assignment]; lgtm | lgtm | tst.go:33:1:33:45 | suppression range | +| tst.go:33:8:33:45 | comment | lgtm[go/redundant-assignment]; lgtm | lgtm[go/redundant-assignment] | tst.go:33:1:33:45 | suppression range | +| tstWindows.go:5:8:5:14 | comment | lgtm | lgtm | tstWindows.go:5:1:5:14 | suppression range | +| tstWindows.go:6:8:6:39 | comment | lgtm[go/redundant-assignment] | lgtm[go/redundant-assignment] | tstWindows.go:6:1:6:39 | suppression range | +| tstWindows.go:7:8:7:39 | comment | lgtm[go/redundant-assignment] | lgtm[go/redundant-assignment] | tstWindows.go:7:1:7:39 | suppression range | +| tstWindows.go:8:8:8:63 | comment | lgtm[go/redundant-assignment, go/redundant-operation] | lgtm[go/redundant-assignment, go/redundant-operation] | tstWindows.go:8:1:8:63 | suppression range | +| tstWindows.go:9:8:9:29 | comment | lgtm[@tag:nullness] | lgtm[@tag:nullness] | tstWindows.go:9:1:9:29 | suppression range | +| tstWindows.go:10:8:10:53 | comment | lgtm[@tag:nullness,go/redundant-assignment] | lgtm[@tag:nullness,go/redundant-assignment] | tstWindows.go:10:1:10:53 | suppression range | +| tstWindows.go:11:8:11:35 | comment | lgtm[@expires:2017-06-11] | lgtm[@expires:2017-06-11] | tstWindows.go:11:1:11:35 | suppression range | +| tstWindows.go:12:8:12:70 | comment | lgtm[go/redundant-operation] because I know better than lgtm | lgtm[go/redundant-operation] | tstWindows.go:12:1:12:70 | suppression range | +| tstWindows.go:13:8:13:25 | comment | lgtm: blah blah | lgtm | tstWindows.go:13:1:13:25 | suppression range | +| tstWindows.go:14:8:14:39 | comment | lgtm blah blah #falsepositive | lgtm | tstWindows.go:14:1:14:39 | suppression range | +| tstWindows.go:15:8:15:39 | comment | lgtm [go/redundant-operation] | lgtm [go/redundant-operation] | tstWindows.go:15:1:15:39 | suppression range | +| tstWindows.go:17:8:17:16 | comment | lgtm[] | lgtm[] | tstWindows.go:17:1:17:16 | suppression range | +| tstWindows.go:19:8:19:13 | comment | lgtm | lgtm | tstWindows.go:19:1:19:13 | suppression range | +| tstWindows.go:20:8:20:14 | comment | \tlgtm | lgtm | tstWindows.go:20:1:20:14 | suppression range | +| tstWindows.go:21:8:21:40 | comment | lgtm\t[go/redundant-assignment] | lgtm\t[go/redundant-assignment] | tstWindows.go:21:1:21:40 | suppression range | +| tstWindows.go:24:8:24:19 | comment | foo; lgtm | lgtm | tstWindows.go:24:1:24:19 | suppression range | +| tstWindows.go:25:8:25:44 | comment | foo; lgtm[go/redundant-assignment] | lgtm[go/redundant-assignment] | tstWindows.go:25:1:25:44 | suppression range | +| tstWindows.go:27:8:27:43 | comment | foo lgtm[go/redundant-assignment] | lgtm[go/redundant-assignment] | tstWindows.go:27:1:27:43 | suppression range | +| tstWindows.go:29:8:29:47 | comment | foo lgtm[go/redundant-assignment] bar | lgtm[go/redundant-assignment] | tstWindows.go:29:1:29:47 | suppression range | +| tstWindows.go:30:8:30:15 | comment | LGTM! | LGTM | tstWindows.go:30:1:30:15 | suppression range | +| tstWindows.go:31:8:31:39 | comment | LGTM[go/redundant-assignment] | LGTM[go/redundant-assignment] | tstWindows.go:31:1:31:39 | suppression range | +| tstWindows.go:32:8:32:72 | comment | lgtm[go/redundant-assignment] and lgtm[go/redundant-operation] | lgtm[go/redundant-assignment] | tstWindows.go:32:1:32:72 | suppression range | +| tstWindows.go:32:8:32:72 | comment | lgtm[go/redundant-assignment] and lgtm[go/redundant-operation] | lgtm[go/redundant-operation] | tstWindows.go:32:1:32:72 | suppression range | +| tstWindows.go:33:8:33:45 | comment | lgtm[go/redundant-assignment]; lgtm | lgtm | tstWindows.go:33:1:33:45 | suppression range | +| tstWindows.go:33:8:33:45 | comment | lgtm[go/redundant-assignment]; lgtm | lgtm[go/redundant-assignment] | tstWindows.go:33:1:33:45 | suppression range | diff --git a/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref b/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref new file mode 100644 index 00000000..9d7833ec --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/AlertSuppression.qlref @@ -0,0 +1 @@ +AlertSuppression.ql diff --git a/ql/test/query-tests/AlertSuppression/AlertSuppressionExample.go b/ql/test/query-tests/AlertSuppression/AlertSuppressionExample.go new file mode 100644 index 00000000..c6cd3693 --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/AlertSuppressionExample.go @@ -0,0 +1,15 @@ +package main + +import "testing" + +func login(user, password string) bool { + return true +} + +func TestLogin(t *testing.T) { + user := "testuser" + password := "horsebatterystaplecorrect" // lgtm[go/hardcoded-credentials] + if !login(user, password) { + t.Errorf("Login test failed.") + } +} diff --git a/ql/test/query-tests/AlertSuppression/tst.go b/ql/test/query-tests/AlertSuppression/tst.go new file mode 100644 index 00000000..c58f468a --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/tst.go @@ -0,0 +1,34 @@ +package main + +func main() { + x := 42 + x = x // lgtm + x = x // lgtm[go/redundant-assignment] + x = x // lgtm[go/redundant-assignment] + x = x // lgtm[go/redundant-assignment, go/redundant-operation] + x = x // lgtm[@tag:nullness] + x = x // lgtm[@tag:nullness,go/redundant-assignment] + x = x // lgtm[@expires:2017-06-11] + x = x // lgtm[go/redundant-operation] because I know better than lgtm + x = x // lgtm: blah blah + x = x // lgtm blah blah #falsepositive + x = x //lgtm [go/redundant-operation] + x = x /* lgtm */ + x = x // lgtm[] + x = x // lgtmfoo + x = x //lgtm + x = x // lgtm + x = x // lgtm [go/redundant-assignment] + x = x // foolgtm[go/redundant-assignment] + x = x // foolgtm + x = x // foo; lgtm + x = x // foo; lgtm[go/redundant-assignment] + x = x // foo lgtm + x = x // foo lgtm[go/redundant-assignment] + x = x // foo lgtm bar + x = x // foo lgtm[go/redundant-assignment] bar + x = x // LGTM! + x = x // LGTM[go/redundant-assignment] + x = x // lgtm[go/redundant-assignment] and lgtm[go/redundant-operation] + x = x // lgtm[go/redundant-assignment]; lgtm +} diff --git a/ql/test/query-tests/AlertSuppression/tstWindows.go b/ql/test/query-tests/AlertSuppression/tstWindows.go new file mode 100644 index 00000000..cfd1da1b --- /dev/null +++ b/ql/test/query-tests/AlertSuppression/tstWindows.go @@ -0,0 +1,34 @@ +package main + +func winMain() { + x := 42 + x = x // lgtm + x = x // lgtm[go/redundant-assignment] + x = x // lgtm[go/redundant-assignment] + x = x // lgtm[go/redundant-assignment, go/redundant-operation] + x = x // lgtm[@tag:nullness] + x = x // lgtm[@tag:nullness,go/redundant-assignment] + x = x // lgtm[@expires:2017-06-11] + x = x // lgtm[go/redundant-operation] because I know better than lgtm + x = x // lgtm: blah blah + x = x // lgtm blah blah #falsepositive + x = x //lgtm [go/redundant-operation] + x = x /* lgtm */ + x = x // lgtm[] + x = x // lgtmfoo + x = x //lgtm + x = x // lgtm + x = x // lgtm [go/redundant-assignment] + x = x // foolgtm[go/redundant-assignment] + x = x // foolgtm + x = x // foo; lgtm + x = x // foo; lgtm[go/redundant-assignment] + x = x // foo lgtm + x = x // foo lgtm[go/redundant-assignment] + x = x // foo lgtm bar + x = x // foo lgtm[go/redundant-assignment] bar + x = x // LGTM! + x = x // LGTM[go/redundant-assignment] + x = x // lgtm[go/redundant-assignment] and lgtm[go/redundant-operation] + x = x // lgtm[go/redundant-assignment]; lgtm +} diff --git a/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.expected b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.expected new file mode 100644 index 00000000..58480071 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.expected @@ -0,0 +1,4 @@ +| InconsistentLoopOrientation.go:10:34:10:36 | decrement statement | This loop counts downward, but its variable is $@ upward. | InconsistentLoopOrientation.go:10:22:10:31 | ...<... | bounded | +| main.go:9:30:9:32 | decrement statement | This loop counts downward, but its variable is $@ upward. | main.go:9:18:9:27 | ...<... | bounded | +| main.go:14:32:14:34 | increment statement | This loop counts upward, but its variable is $@ downward. | main.go:14:25:14:29 | ...>... | bounded | +| main.go:25:34:25:36 | decrement statement | This loop counts downward, but its variable is $@ upward. | main.go:25:22:25:31 | ...<... | bounded | diff --git a/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.go b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.go new file mode 100644 index 00000000..077015ce --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.go @@ -0,0 +1,13 @@ +package main + +func zeroOutExceptBad(a []int, lower int, upper int) { + // zero out everything below index `lower` + for i := lower - 1; i >= 0; i-- { + a[i] = 0 + } + + // zero out everything above index `upper` + for i := upper + 1; i < len(a); i-- { // NOT OK + a[i] = 0 + } +} diff --git a/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.qlref b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.qlref new file mode 100644 index 00000000..62ab35e2 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientation.qlref @@ -0,0 +1 @@ +InconsistentCode/InconsistentLoopOrientation.ql diff --git a/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientationGood.go b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientationGood.go new file mode 100644 index 00000000..31d6bf08 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/InconsistentLoopOrientationGood.go @@ -0,0 +1,13 @@ +package main + +func zeroOutExceptGood(a []int, lower int, upper int) { + // zero out everything below index `lower` + for i := lower - 1; i >= 0; i-- { + a[i] = 0 + } + + // zero out everything above index `upper` + for i := upper + 1; i < len(a); i++ { + a[i] = 0 + } +} diff --git a/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/main.go b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/main.go new file mode 100644 index 00000000..ede1c587 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/InconsistentLoopOrientation/main.go @@ -0,0 +1,42 @@ +package main + +func f1(i int) { + for j := i - 1; j >= 0; j-- { // OK + } +} + +func f2(i int, s string) { + for j := i + 1; j < len(s); j-- { // NOT OK + } +} + +func f3(s string) { + for i, l := 0, len(s); i > l; i++ { // NOT OK + } +} + +func f4(lower int, a []int) { + for i := lower - 1; i >= 0; i-- { // OK + a[i] = 0 + } +} + +func f5(upper int, a []int) { + for i := upper + 1; i < len(a); i-- { // NOT OK + a[i] = 0 + } +} + +func f6(upper uint, a []int) { + for i := upper + 1; i < uint(len(a)); i-- { // NOT OK, but not currently flagged + a[i] = 0 + } +} + +func f7(a []int) { + for i := uint(len(a)) - 1; i < uint(len(a)); i-- { // OK + a[i] = 0 + } +} + +func main() {} diff --git a/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.expected b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.expected new file mode 100644 index 00000000..156b5860 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.expected @@ -0,0 +1,3 @@ +| LengthComparisonOffByOne.go:8:14:8:29 | ...<=... | Off-by-one index comparison against length may lead to out-of-bounds $@. | LengthComparisonOffByOne.go:10:6:10:14 | index expression | read | +| main.go:6:5:6:15 | ...<=... | Off-by-one index comparison against length may lead to out-of-bounds $@. | main.go:7:10:7:13 | index expression | read | +| main.go:29:5:29:14 | ...>... | Off-by-one index comparison against length may lead to out-of-bounds $@. | main.go:30:10:30:13 | index expression | read | diff --git a/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.go b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.go new file mode 100644 index 00000000..7db63c62 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.go @@ -0,0 +1,15 @@ +package main + +import "strings" + +func containsBad(searchName string, names string) bool { + values := strings.Split(names, ",") + // BAD: index could be equal to length + for i := 0; i <= len(values); i++ { + // When i = length, this access will be out of bounds + if values[i] == searchName { + return true + } + } + return false +} diff --git a/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.qlref b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.qlref new file mode 100644 index 00000000..8692ba8a --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOne.qlref @@ -0,0 +1 @@ +InconsistentCode/LengthComparisonOffByOne.ql diff --git a/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOneGood.go b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOneGood.go new file mode 100644 index 00000000..6c0bcb46 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/LengthComparisonOffByOneGood.go @@ -0,0 +1,14 @@ +package main + +import "strings" + +func containsGood(searchName string, names string) bool { + values := strings.Split(names, ",") + // GOOD: Avoid using indexes, use range loop instead + for _, name := range values { + if name == searchName { + return true + } + } + return true +} diff --git a/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/main.go b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/main.go new file mode 100644 index 00000000..3a426dc5 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/LengthComparisonOffByOne/main.go @@ -0,0 +1,86 @@ +package main + +import "regexp" + +func f1(i int, a []int) int { + if i <= len(a) { // NOT OK + return a[i] + } + return -1 +} + +func f2(i int, a []int) int { + if i < len(a) { // OK + return a[i] + } + return -1 +} + +func f3(i int, a []int) int { + if i <= len(a) { // OK + if i != len(a) { + return a[i] + } + } + return -1 +} + +func f4(i int, a []int) int { + if len(a) > 0 { // NOT OK + return a[1] + } + return -1 +} + +func f5(i int, a []int) int { + if len(a) > 1 { // OK + return a[1] + } + return -1 +} + +func f6(i int, a []int) int { + if len(a) > 0 { // OK + if len(a) > 1 { + return a[0] + } + } + return -1 +} + +func f7(i int, a map[int]int) int { + if i <= len(a) { // OK + return a[i] + } + return -1 +} + +func f8(s string) string { + r := regexp.MustCompile("(.*?),(.*?)") + m := r.FindStringSubmatch(s) + if len(m) > 0 { // OK + return m[1] + } + return "" +} + +func f9(xs []int) int { + if len(xs) > 1 { + if len(xs) != 3 || f5(0, xs) == -1 { + return 0 + } + return xs[2] + } + return -1 +} + +type intintmap map[int]int + +func f10(i int, a intintmap) int { + if i <= len(a) { // OK + return a[i] + } + return -1 +} + +func main() {} diff --git a/ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.expected b/ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.expected new file mode 100644 index 00000000..171df10b --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.expected @@ -0,0 +1,6 @@ +| MistypedExponentiation.go:6:14:6:19 | ...^... | This expression uses the bitwise exclusive-or operator when exponentiation was likely meant. | +| main.go:15:14:15:18 | ...^... | This expression uses the bitwise exclusive-or operator when exponentiation was likely meant. | +| main.go:17:14:17:19 | ...^... | This expression uses the bitwise exclusive-or operator when exponentiation was likely meant. | +| main.go:18:14:18:19 | ...^... | This expression uses the bitwise exclusive-or operator when exponentiation was likely meant. | +| main.go:19:14:19:21 | ...^... | This expression uses the bitwise exclusive-or operator when exponentiation was likely meant. | +| main.go:21:14:21:22 | ...^... | This expression uses the bitwise exclusive-or operator when exponentiation was likely meant. | diff --git a/ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.go b/ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.go new file mode 100644 index 00000000..f6e3108f --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func test() { + fmt.Println(2 ^ 32) // should be 1 << 32 +} diff --git a/ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.qlref b/ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.qlref new file mode 100644 index 00000000..bd96eb93 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/MistypedExponentiation/MistypedExponentiation.qlref @@ -0,0 +1 @@ +InconsistentCode/MistypedExponentiation.ql diff --git a/ql/test/query-tests/InconsistentCode/MistypedExponentiation/main.go b/ql/test/query-tests/InconsistentCode/MistypedExponentiation/main.go new file mode 100644 index 00000000..a749a809 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/MistypedExponentiation/main.go @@ -0,0 +1,27 @@ +package main + +import "fmt" + +// analysis does not detect calls to this function as constant (yet) +func nonconst() int { + return 0 +} + +func main() { + exp := 3 + expectingResponse := 1 << 5 + power := 10 + + fmt.Println(3 ^ 5) // Not OK + fmt.Println(0755 ^ 2423) // OK + fmt.Println(2 ^ 32) // Not OK + fmt.Println(10 ^ 5) // Not OK + fmt.Println(10 ^ exp) // Not OK + fmt.Println(253 ^ expectingResponse) // OK + fmt.Println(2 ^ power) // Not OK + + // This is not ok, but isn't detected because the multiplication binds tighter + // than the xor operator and so the query doesn't see a constant on the left + // hand side of ^. + fmt.Println(nonconst()*10 ^ 9) +} diff --git a/ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.expected b/ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.expected new file mode 100644 index 00000000..dc3b7057 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.expected @@ -0,0 +1,2 @@ +| WhitespaceContradictsPrecedence.go:4:9:4:18 | ...<<... | & is evaluated before <<, but whitespace suggests the opposite. | +| main.go:12:9:12:16 | ...+... | >> is evaluated before +, but whitespace suggests the opposite. | diff --git a/ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.go b/ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.go new file mode 100644 index 00000000..462cc8b7 --- /dev/null +++ b/ql/test/query-tests/InconsistentCode/WhitespaceContradictsPrecedence/WhitespaceContradictsPrecedence.go @@ -0,0 +1,5 @@ +package main + +func isBitSetBad(x int, pos uint) bool { + return x & 1<>1; +} + +func ok2(x int) int { + return x + x >> 1; +} + +func bad(x int) int { + return x+x >> 1; +} + +func ok3(x int) int { + return x + (x>>1); +} + +func ok4(x int, y int, z int) int { + return x + y + z; +} + +func ok5(x int, y int, z int) int { + return x + y+z; +} + +func ok6(x int) int { + return x + x>> 1; +} + +func ok7(x int, y int, z int) int { + return x + y - z; +} + +func ok8(x int, y int, z int) int { + return x + y-z; +} + +func ok9(x int, y int, z int) int { + return x * y*z; +} + +func main() {} diff --git a/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.expected b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.expected new file mode 100644 index 00000000..7bfe348c --- /dev/null +++ b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.expected @@ -0,0 +1,3 @@ +| CompareIdenticalValues.go:9:3:9:8 | ...<=... | This expression compares $@ to itself. | CompareIdenticalValues.go:9:3:9:3 | y | an expression | +| tst.go:6:9:6:14 | ...==... | This expression compares $@ to itself. | tst.go:6:9:6:9 | x | an expression | +| vp.go:16:9:16:38 | ...!=... | This expression compares $@ to itself. | vp.go:16:9:16:21 | call to GetLength | an expression | diff --git a/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.go b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.go new file mode 100644 index 00000000..b096cdf5 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.go @@ -0,0 +1,12 @@ +package main + +type Rectangle struct { + x, y, width, height int +} + +func (r *Rectangle) containsBad(x, y int) bool { + return r.x <= x && + y <= y && // NOT OK + x <= r.x+r.width && + y <= r.y+r.height +} diff --git a/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.qlref b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.qlref new file mode 100644 index 00000000..7c3ac7ac --- /dev/null +++ b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValues.qlref @@ -0,0 +1 @@ +RedundantCode/CompareIdenticalValues.ql diff --git a/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValuesGood.go b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValuesGood.go new file mode 100644 index 00000000..d0430dfc --- /dev/null +++ b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/CompareIdenticalValuesGood.go @@ -0,0 +1,8 @@ +package main + +func (r *Rectangle) containsGood(x, y int) bool { + return r.x <= x && + r.y <= y && + x <= r.x+r.width && + y <= r.y+r.height +} diff --git a/ql/test/query-tests/RedundantCode/CompareIdenticalValues/constants.go b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/constants.go new file mode 100644 index 00000000..d999a815 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/constants.go @@ -0,0 +1,5 @@ +// +build linux, amd64 + +package main + +const ptrsize = 8 diff --git a/ql/test/query-tests/RedundantCode/CompareIdenticalValues/tst.go b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/tst.go new file mode 100644 index 00000000..db44c5df --- /dev/null +++ b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/tst.go @@ -0,0 +1,39 @@ +package main + +import "fmt" + +func foo(x int) bool { + return x == x // NOT OK +} + +func isNaN(x float32) bool { + return x != x // OK +} + +func main() { + foo(42) +} + +const mode = "Debug" + +func log(msg string) { + if mode == "Debug" { + fmt.Println(msg) + } +} + +func bar() { + if ptrsize == 8 { // OK + fmt.Println() + } +} + +func bump(x *int) { + *x++ +} + +func baz() bool { + var x = 0 + bump(&x) + return x == 0 +} diff --git a/ql/test/query-tests/RedundantCode/CompareIdenticalValues/vp.go b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/vp.go new file mode 100644 index 00000000..64e070e6 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/CompareIdenticalValues/vp.go @@ -0,0 +1,17 @@ +package main + +type baseT struct { + length int +} + +func (x *baseT) GetLength() int { + return x.length +} + +type t struct { + baseT +} + +func (x *t) foo(other t) bool { + return x.GetLength() != x.GetLength() +} diff --git a/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.expected b/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.expected new file mode 100644 index 00000000..68935b96 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.expected @@ -0,0 +1 @@ +| DeadStoreOfField.go:8:2:8:6 | assignment to field val | This assignment to val is useless since its value is never read. | diff --git a/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.go b/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.go new file mode 100644 index 00000000..b74b7312 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.go @@ -0,0 +1,9 @@ +package main + +type counter struct { + val int +} + +func (w counter) reset() { + w.val = 0 // NOT OK +} diff --git a/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.qlref b/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.qlref new file mode 100644 index 00000000..90aa8beb --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfField.qlref @@ -0,0 +1 @@ +RedundantCode/DeadStoreOfField.ql diff --git a/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfFieldGood.go b/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfFieldGood.go new file mode 100644 index 00000000..a26f0064 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DeadStoreOfField/DeadStoreOfFieldGood.go @@ -0,0 +1,5 @@ +package main + +func (w *counter) resetGood() { + w.val = 0 // OK +} diff --git a/ql/test/query-tests/RedundantCode/DeadStoreOfField/main.go b/ql/test/query-tests/RedundantCode/DeadStoreOfField/main.go new file mode 100644 index 00000000..8ea70820 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DeadStoreOfField/main.go @@ -0,0 +1,72 @@ +package main + +type wrapper struct { + s string + hash int +} + +func mkWrapper() (w wrapper) { + w.hash = -1 // OK + return +} + +func (w wrapper) reset2() wrapper { + w.hash = -1 // OK + return w +} + +func (w wrapper) reset3(out *wrapper) { + w.hash = -1 // OK + *out = w +} + +func sameHash(w1, w2 wrapper) bool { + w1.s = "" // OK + w2.s = "" // OK + return w1 == w2 +} + +func lookup(cs map[wrapper]int, w wrapper) int { + w.hash = -1 // OK + return cs[w] +} + +func send(ch chan<- wrapper, w wrapper) { + w.hash = -1 // OK + ch <- w +} + +func test() (w wrapper) { + defer (func() { + w.hash = -1 // OK + })() + return wrapper{} +} + +func test2() (w wrapper) { + w.hash = -1 // NOT OK, but not currently flagged because w is a result variable + return wrapper{"hi", 0} +} + +var cbs = make([]func() string, 10, 10) + +func (w *wrapper) getString() string { + return w.s +} + +func test3() { + w := wrapper{"hi", 0} + w.hash = -1 // OK + cbs = append(cbs, w.getString) +} + +type wrapperPtr *wrapper + +func test4(p wrapperPtr) { + p.hash = -1 // OK +} + +func test5(w wrapper) int { + w.hash = -1 + return w.hash +} diff --git a/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/DeadStoreOfLocal.expected b/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/DeadStoreOfLocal.expected new file mode 100644 index 00000000..26e43880 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/DeadStoreOfLocal.expected @@ -0,0 +1,29 @@ +| main.go:25:2:25:2 | assignment to x | This definition of x is never used. | +| testdata.go:32:2:32:2 | assignment to x | This definition of x is never used. | +| testdata.go:37:2:37:2 | assignment to x | This definition of x is never used. | +| testdata.go:61:2:61:2 | assignment to x | This definition of x is never used. | +| testdata.go:67:2:67:2 | assignment to x | This definition of x is never used. | +| testdata.go:99:2:99:2 | assignment to x | This definition of x is never used. | +| testdata.go:101:3:101:3 | assignment to x | This definition of x is never used. | +| testdata.go:108:2:108:2 | assignment to x | This definition of x is never used. | +| testdata.go:110:3:110:3 | assignment to x | This definition of x is never used. | +| testdata.go:128:2:128:2 | assignment to x | This definition of x is never used. | +| testdata.go:130:3:130:3 | assignment to x | This definition of x is never used. | +| testdata.go:131:3:131:3 | assignment to x | This definition of x is never used. | +| testdata.go:134:3:134:3 | assignment to x | This definition of x is never used. | +| testdata.go:143:3:143:3 | assignment to x | This definition of x is never used. | +| testdata.go:164:3:164:3 | assignment to x | This definition of x is never used. | +| testdata.go:172:3:172:3 | assignment to x | This definition of x is never used. | +| testdata.go:180:3:180:5 | increment statement | This definition of x is never used. | +| testdata.go:201:2:201:2 | assignment to x | This definition of x is never used. | +| testdata.go:262:2:262:2 | assignment to x | This definition of x is never used. | +| testdata.go:268:2:268:2 | assignment to x | This definition of x is never used. | +| testdata.go:309:2:309:2 | assignment to a | This definition of a is never used. | +| testdata.go:321:2:321:2 | assignment to a | This definition of a is never used. | +| testdata.go:385:3:385:3 | assignment to x | This definition of x is never used. | +| testdata.go:430:3:430:3 | assignment to x | This definition of x is never used. | +| testdata.go:432:3:432:3 | assignment to x | This definition of x is never used. | +| testdata.go:439:2:439:2 | assignment to x | This definition of x is never used. | +| testdata.go:486:3:486:3 | assignment to x | This definition of x is never used. | +| testdata.go:540:3:540:3 | assignment to x | This definition of x is never used. | +| testdata.go:578:4:578:4 | assignment to x | This definition of x is never used. | diff --git a/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/DeadStoreOfLocal.qlref b/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/DeadStoreOfLocal.qlref new file mode 100644 index 00000000..9acb5d81 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/DeadStoreOfLocal.qlref @@ -0,0 +1 @@ +RedundantCode/DeadStoreOfLocal.ql diff --git a/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/main.go b/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/main.go new file mode 100644 index 00000000..3bb3c390 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/main.go @@ -0,0 +1,33 @@ +package p + +import "fmt" + +func test() { + if false { + x := deadStore() // OK + fmt.Println(x) + x++ // NOT OK, but in dead code, so not flagged + } +} + +func deref(p *int) { + fmt.Println(*p) +} + +func main() { + var x int + p := &x + x = deadStore() + deref(p) +} + +func deadParameter(x int) bool { // we don't want to flag x here + x = deadStore() // but we do want to flag this + return true +} + +func test2(x int) (int, int) { + y := x >> 5 + z := x % (1) + return z, y % 13 +} diff --git a/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/testdata.go b/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/testdata.go new file mode 100644 index 00000000..450204c8 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DeadStoreOfLocal/testdata.go @@ -0,0 +1,611 @@ +// adapted from https://raw.githubusercontent.com/gordonklaus/ineffassign/master/testdata/testdata.go + +package p + +var b bool + +func deadStore() int { + return 0 +} + +func deadStore2() string { + return "abc" +} + +func _() { + var x int + x = 0 + _ = x +} + +func _() { + var x int + x = 0 + if b { + _ = x + } +} + +func _() { + var x int + _ = x + x = deadStore() // BAD +} + +func _() { + var x int + x = deadStore() // BAD + x = 0 + _ = x +} + +func _() { + x := 0 + x = 0 + _ = x +} + +func _() { + x := int64(0) + x = 0 + _ = x +} + +func _() { + x := "" + x = "abc" + _ = x +} + +func _() { + x := deadStore2() // BAD + x = "def" + _ = x +} + +func _() { + x := deadStore() // BAD + x = 0 + _ = x +} + +func _() { + x := 0 + x = x + 0 + _ = x +} + +func _() { + x := 0 + x += 0 + _ = x +} + +func _() { + x := 0 + x++ + _ = x +} + +func _() { + x := 0 + if b { + x = 0 + } + _ = x +} + +func _() { + x := deadStore() // BAD + if b { + x = deadStore() // BAD + } + x = 0 + _ = x +} + +func _() { + x := deadStore() // BAD + for b { + x = deadStore() // BAD + } + x = 0 + _ = x +} + +func _() { + x := 0 + if b { + x = 0 + } + if b { + x = 0 + } + _ = x +} + +func _() { + x := deadStore() // BAD + if b { + x = deadStore() // BAD + x = deadStore() // BAD + } + if b { + x = deadStore() // BAD + } + x = 0 + _ = x +} + +func _() { + x := 0 + if b { + x = deadStore() // BAD + x = 0 + } + if b { + x = 0 + } + _ = x +} + +func _() { + x := 0 + for { + _ = x + x = 0 + } +} + +func _() { + x := 0 + for { + _ = x + x = deadStore() // BAD + x = 0 + } +} + +func _() { + x := 0 + for { + x += deadStore() // BAD + x = 0 + } +} + +func _() { + x := 0 + for { + x++ // BAD + x = 0 + } +} + +func _() { + x := 0 + for { + x++ + } +} + +func _() { + x := 0 + _ = &x + x = 0 +} + +func _() { + x := struct{ f int }{42} + _ = x.f + x = struct{ f int }{23} +} + +func _() { + x := []int{2} + _ = &x[0] + x = []int{} +} + +func _() { + x := []int{} + _ = x[:] + x = []int{} +} + +func _() { + x := 0 + func() { + _ = x + }() + x = 0 +} + +func _() { + x := 0 + func() { + x++ + }() + x = 0 +} + +func _() { + x := 0 + func() { + x += 0 + }() + x = 0 +} + +func _() { + x := 0 + func() { + x = 0 + }() + _ = x +} + +func _() { + x := 0 + _ = x + func() { + x = 0 + }() +} + +func _() (x int) { + x = 0 + return +} + +func _() (x int) { + x = deadStore() // BAD + x = 0 + return +} + +func _() (x int) { + x = deadStore() // BAD + return 0 +} + +func _() (x int) { + x = 0 + return x +} + +func _() (x int) { + x = 1 + anyFunctionMightPanic() + return 2 +} + +func _(a []int, i int) (x int) { + x = 1 + _ = a[i] + return 2 +} + +func _(a []string, i int, j int) (x int) { + x = 1 + _ = a[i:j] + return 2 +} + +func _(a float32, b float32) (x int) { + x = 1 + _ = a == b + return 2 +} + +func _(a float32, b float32) (x int) { + x = 1 + _ = a / b + return 2 +} + +func _(a float32, b float32) (x int) { + x = 1 + a /= b + return 2 +} + +func _(a int, b int) (x int) { + x = 1 + _ = a % b + return 2 +} + +func _(a int, b int) (x int) { + x = 1 + a %= b + return 2 +} + +func _(a struct{ b int }) (x int) { + x = 1 + _ = a.b + return 2 +} + +func _(a *int) (x int) { + x = 1 + _ = *a + return 2 +} + +func _(a interface{}) (x int) { + x = 1 + _ = a.(int) + return 2 +} + +func _(a chan int, b int) (x int) { + x = 1 + a <- b + return 2 +} + +func _() { + global = 0 +} + +var global int + +func _() { + global = 0 + global = 0 +} + +func _() { + var x int + if b { + x = 0 + } else { + x = 0 + } + _ = x +} + +func _() { + var x int + switch b { + case true: + x = 0 + case false: + x = 0 + } + _ = x +} + +func _() { + var x int + switch b { + default: + x = deadStore() // BAD + fallthrough + case b: + } +} + +func _() { + var x int + switch b { + default: + x = 0 + fallthrough + case b: + _ = x + } +} + +func _() { + var x int + switch interface{}(b).(type) { + case bool: + x = 0 + case int: + x = 0 + } + _ = x +} + +func _() { + var x int + var ch chan int + select { + case ch <- 0: + x = 0 + case <-ch: + x = 0 + } + _ = x +} + +func _() { + var x int + var ch chan int + select { + case ch <- 0: + x = deadStore() // BAD + case <-ch: + x = deadStore() // BAD + default: + _ = x + } +} + +func _() { + x := deadStore() // BAD + var ch chan int + select { + case ch <- 0: + x = 0 + case <-ch: + x = 0 + default: + x = 0 + } + _ = x +} + +func _() { + x := 0 + var ch chan int + select { + case ch <- 0: + x = 0 + case <-ch: + x = 0 + } + _ = x +} + +func _() (x int) { + if b { + x = 0 + return + } + x = 0 + return +} + +func _() { + var x int + if b { + x = 0 + } else if x = 0; b { + + } + _ = x +} + +func _() { + var x int + if b { + x = deadStore() // BAD + } + if x = 0; b { + + } + _ = x +} + +func _() { + var x int + if b { + x = 0 + } else if b { + x = 0 + } + _ = x +} + +func _() { + var x int + if b { + x = 0 + } else { + x = 0 + } + _ = x +} + +func _() { + x := 0 + for b { + _ = x + x = 0 + } + x = 0 + _ = x +} + +func _() { + x := 0 + for { + _ = x + x = 0 + if b { + break + } + x = 0 + } + _ = x +} + +func _() { + x := 0 + for x < 0 { + x = deadStore() // BAD + if b { + break + } + x = 0 + } +} + +func _() { + x := 0 + for { + _ = x + x = 0 + if b { + continue + } + x = 0 + } +} + +func _() { + var x int + for x = range []int{} { + _ = x + x = 0 + if b { + continue + } else { + break + } + } + _ = x +} + +func _() { + var x int + for { + if b { + x = deadStore() // BAD + break + } + _ = x + } +} + +func _() { + var x int +loop: + for b { + _ = x + for b { + x = 0 + break loop + } + x = 0 + } + _ = x +} + +func _() { + var x int + for range []int{} { + if b { + x = 0 + continue + } + x = 0 + } + _ = x +} + +func anyFunctionMightPanic() diff --git a/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.expected b/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.expected new file mode 100644 index 00000000..5df6faa3 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.expected @@ -0,0 +1,2 @@ +| DuplicateBranches.go:4:5:4:10 | ...>=... | The 'then' and 'else' branches of this if statement are identical. | +| main.go:6:5:6:9 | ...<... | The 'then' and 'else' branches of this if statement are identical. | diff --git a/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.go b/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.go new file mode 100644 index 00000000..f4bc36b6 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.go @@ -0,0 +1,9 @@ +package main + +func abs(x int) int { + if x >= 0 { + return x + } else { + return x + } +} diff --git a/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.qlref b/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.qlref new file mode 100644 index 00000000..3eb10d9d --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranches.qlref @@ -0,0 +1 @@ +RedundantCode/DuplicateBranches.ql diff --git a/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranchesGood.go b/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranchesGood.go new file mode 100644 index 00000000..9497c7a1 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateBranches/DuplicateBranchesGood.go @@ -0,0 +1,9 @@ +package main + +func absGood(x int) int { + if x >= 0 { + return x + } else { + return -x + } +} diff --git a/ql/test/query-tests/RedundantCode/DuplicateBranches/main.go b/ql/test/query-tests/RedundantCode/DuplicateBranches/main.go new file mode 100644 index 00000000..0a524b09 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateBranches/main.go @@ -0,0 +1,29 @@ +package main + +import "fmt" + +func bad(x int) { + if x < 0 { // NOT OK + fmt.Println("x is negative") + } else { + fmt.Println("x is negative") + } +} + +func good(x int) { + if x < 0 { // OK + fmt.Println("x is negative") + } else { + fmt.Println("x is non-negative") + } +} + +func good2(xs []int, off int, b bool) []int { + if b { + return xs[off:] + } else { + return xs[:off] + } +} + +func main() {} diff --git a/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.expected b/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.expected new file mode 100644 index 00000000..c01d55f0 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.expected @@ -0,0 +1,2 @@ +| DuplicateCondition.go:6:12:6:25 | ...==... | This condition is a duplicate of $@. | DuplicateCondition.go:4:5:4:18 | ...==... | an earlier condition | +| tst.go:9:12:9:13 | ok | This condition is a duplicate of $@. | tst.go:8:22:8:23 | ok | an earlier condition | diff --git a/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.go b/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.go new file mode 100644 index 00000000..a93bb546 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.go @@ -0,0 +1,11 @@ +package main + +func controller(msg string) { + if msg == "start" { + start() + } else if msg == "start" { // NOT OK + stop() + } else { + panic("Message not understood.") + } +} diff --git a/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.qlref b/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.qlref new file mode 100644 index 00000000..a6069ea9 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateCondition.qlref @@ -0,0 +1 @@ +RedundantCode/DuplicateCondition.ql diff --git a/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateConditionGood.go b/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateConditionGood.go new file mode 100644 index 00000000..a2a4e194 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateCondition/DuplicateConditionGood.go @@ -0,0 +1,11 @@ +package main + +func controllerGood(msg string) { + if msg == "start" { + start() + } else if msg == "stop" { + stop() + } else { + panic("Message not understood.") + } +} diff --git a/ql/test/query-tests/RedundantCode/DuplicateCondition/funcs.go b/ql/test/query-tests/RedundantCode/DuplicateCondition/funcs.go new file mode 100644 index 00000000..6d6de31b --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateCondition/funcs.go @@ -0,0 +1,5 @@ +package main + +func start() {} + +func stop() {} \ No newline at end of file diff --git a/ql/test/query-tests/RedundantCode/DuplicateCondition/tst.go b/ql/test/query-tests/RedundantCode/DuplicateCondition/tst.go new file mode 100644 index 00000000..912f13fe --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateCondition/tst.go @@ -0,0 +1,12 @@ +package main + +func check(x int) bool { + return true +} + +func main() { + if ok := check(42); ok { + } else if ok { // NOT OK + } else if ok := check(23); ok { // OK + } +} diff --git a/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.expected b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.expected new file mode 100644 index 00000000..936ff519 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.expected @@ -0,0 +1,2 @@ +| DuplicateSwitchCase.go:7:7:7:20 | ...==... | This case is a duplicate of $@. | DuplicateSwitchCase.go:5:7:5:20 | ...==... | an earlier case | +| tst.go:9:7:9:12 | ...<... | This case is a duplicate of $@. | tst.go:5:7:5:12 | ...<... | an earlier case | diff --git a/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.go b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.go new file mode 100644 index 00000000..1c902c13 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.go @@ -0,0 +1,12 @@ +package main + +func controller(msg string) { + switch { + case msg == "start": + start() + case msg == "start": + stop() + default: + panic("Message not understood.") + } +} diff --git a/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.qlref b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.qlref new file mode 100644 index 00000000..570b78b5 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCase.qlref @@ -0,0 +1 @@ +RedundantCode/DuplicateSwitchCase.ql diff --git a/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCaseGood.go b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCaseGood.go new file mode 100644 index 00000000..19394bd8 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/DuplicateSwitchCaseGood.go @@ -0,0 +1,12 @@ +package main + +func controllerGood(msg string) { + switch { + case msg == "start": + start() + case msg == "stop": + stop() + default: + panic("Message not understood.") + } +} diff --git a/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/funcs.go b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/funcs.go new file mode 100644 index 00000000..6d6de31b --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/funcs.go @@ -0,0 +1,5 @@ +package main + +func start() {} + +func stop() {} \ No newline at end of file diff --git a/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/tst.go b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/tst.go new file mode 100644 index 00000000..c927cd3d --- /dev/null +++ b/ql/test/query-tests/RedundantCode/DuplicateSwitchCase/tst.go @@ -0,0 +1,25 @@ +package main + +func check(x int) { + switch { + case x < 23: + + case x < 42: + + case x < 23: // NOT OK + + } +} + +func check2(value int64) { + switch { + case value < 1024*1024*1024*1024: + + case value < 1024*1024*1024*1024*1024: + + } +} + +func main() { + check(56) +} diff --git a/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.expected b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.expected new file mode 100644 index 00000000..dc40580d --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.expected @@ -0,0 +1,4 @@ +| ExprHasNoEffect.go:13:2:13:13 | call to addDays | This expression has no effect. | +| main.go:26:2:26:7 | call to f1 | This expression has no effect. | +| main.go:28:2:28:11 | call to f1 | This expression has no effect. | +| main.go:29:2:29:8 | call to abs | This expression has no effect. | diff --git a/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.go b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.go new file mode 100644 index 00000000..3c8b85f1 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.go @@ -0,0 +1,15 @@ +package main + +import "fmt" + +type Timestamp int + +func (t Timestamp) addDays(d int) Timestamp { + return Timestamp(int(t) + d*24*3600) +} + +func test(t Timestamp) { + fmt.Printf("Before: %s\n", t) + t.addDays(7) + fmt.Printf("After: %s\n", t) +} diff --git a/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.qlref b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.qlref new file mode 100644 index 00000000..d13ada43 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffect.qlref @@ -0,0 +1 @@ +RedundantCode/ExprHasNoEffect.ql diff --git a/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffectGood.go b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffectGood.go new file mode 100644 index 00000000..1459b880 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/ExprHasNoEffectGood.go @@ -0,0 +1,9 @@ +package main + +import "fmt" + +func testGood(t Timestamp) { + fmt.Printf("Before: %s\n", t) + t = t.addDays(7) + fmt.Printf("After: %s\n", t) +} diff --git a/ql/test/query-tests/RedundantCode/ExprHasNoEffect/main.go b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/main.go new file mode 100644 index 00000000..e9c18030 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/main.go @@ -0,0 +1,37 @@ +package main + +import "fmt" + +func f1(i int) int { + return i +} + +func f2(i int) int { + fmt.Println("hi") + return i +} + +func abs(i int) int { + if i < 0 { + return -i + } + return i +} + +func div(x int, y int) int { + return x / y +} + +func main() { + f1(42) // NOT OK + f2(42) // OK + f1(f2(42)) // NOT OK + abs(-2) // NOT OK + div(1, 0) // OK + dostuff() // OK + cleanup() // OK +} + +func cleanup() { + // nothing to clean up +} diff --git a/ql/test/query-tests/RedundantCode/ExprHasNoEffect/tst.go b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/tst.go new file mode 100644 index 00000000..a81e4c93 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ExprHasNoEffect/tst.go @@ -0,0 +1,7 @@ +// +build linux + +package main + +func dostuff() { + // nothing to do on Linux +} diff --git a/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.expected b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.expected new file mode 100644 index 00000000..72fd7ee8 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.expected @@ -0,0 +1,5 @@ +| NegativeLengthCheck.go:4:5:4:15 | ...<... | 'len' is always non-negative, and hence cannot be less than 0. | +| main.go:6:5:6:20 | ...<... | 'len' is always non-negative, and hence cannot be less than 0. | +| main.go:14:5:14:20 | ...<... | 'cap' is always non-negative, and hence cannot be less than 0. | +| main.go:18:5:18:22 | ...<=... | 'len' is always non-negative, and hence cannot be less than -1. | +| main.go:22:5:22:22 | ...==... | 'len' is always non-negative, and hence cannot equal -1. | diff --git a/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.go b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.go new file mode 100644 index 00000000..6ebdb224 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.go @@ -0,0 +1,8 @@ +package main + +func getFirst(xs []int) int { + if len(xs) < 0 { + panic("No elements provided") + } + return xs[0] +} diff --git a/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.qlref b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.qlref new file mode 100644 index 00000000..d3e9be22 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheck.qlref @@ -0,0 +1 @@ +RedundantCode/NegativeLengthCheck.ql diff --git a/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheckGood.go b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheckGood.go new file mode 100644 index 00000000..0ff61b60 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/NegativeLengthCheckGood.go @@ -0,0 +1,8 @@ +package main + +func getFirstGood(xs []int) int { + if len(xs) == 0 { + panic("No elements provided") + } + return xs[0] +} diff --git a/ql/test/query-tests/RedundantCode/NegativeLengthCheck/main.go b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/main.go new file mode 100644 index 00000000..40c940de --- /dev/null +++ b/ql/test/query-tests/RedundantCode/NegativeLengthCheck/main.go @@ -0,0 +1,25 @@ +package main + +import "os" + +func main() { + if len(os.Args) < 0 { // NOT OK + println("No arguments provided.") + } + + if len(os.Args) <= 0 { // OK + println("No arguments provided.") + } + + if cap(os.Args) < 0 { // NOT OK + println("Out of space!") + } + + if len(os.Args) <= -1 { // NOT OK + println("No arguments provided.") + } + + if len(os.Args) == -1 { // NOT OK + println("No arguments provided.") + } +} diff --git a/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.expected b/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.expected new file mode 100644 index 00000000..678f0857 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.expected @@ -0,0 +1,5 @@ +| RedundantExpr.go:4:10:4:14 | ...+... | The $@ and $@ operand of this operation are identical. | RedundantExpr.go:4:10:4:10 | x | left | RedundantExpr.go:4:14:4:14 | x | right | +| tst.go:4:9:4:13 | ...-... | The $@ and $@ operand of this operation are identical. | tst.go:4:9:4:9 | x | left | tst.go:4:13:4:13 | x | right | +| tst.go:4:31:4:35 | ...&... | The $@ and $@ operand of this operation are identical. | tst.go:4:31:4:31 | x | left | tst.go:4:35:4:35 | x | right | +| tst.go:9:11:9:15 | ...+... | The $@ and $@ operand of this operation are identical. | tst.go:9:11:9:11 | x | left | tst.go:9:15:9:15 | x | right | +| tst.go:20:10:20:14 | ...-... | The $@ and $@ operand of this operation are identical. | tst.go:20:10:20:10 | d | left | tst.go:20:14:20:14 | 1 | right | diff --git a/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.go b/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.go new file mode 100644 index 00000000..033f3883 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.go @@ -0,0 +1,5 @@ +package main + +func avg(x, y float64) float64 { + return (x + x) / 2 +} diff --git a/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.qlref b/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.qlref new file mode 100644 index 00000000..23a5db7b --- /dev/null +++ b/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExpr.qlref @@ -0,0 +1 @@ +RedundantCode/RedundantExpr.ql diff --git a/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExprGood.go b/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExprGood.go new file mode 100644 index 00000000..2874ccbd --- /dev/null +++ b/ql/test/query-tests/RedundantCode/RedundantExpr/RedundantExprGood.go @@ -0,0 +1,5 @@ +package main + +func avgGood(x, y float64) float64 { + return (x + y) / 2 +} diff --git a/ql/test/query-tests/RedundantCode/RedundantExpr/tst.go b/ql/test/query-tests/RedundantCode/RedundantExpr/tst.go new file mode 100644 index 00000000..19721347 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/RedundantExpr/tst.go @@ -0,0 +1,28 @@ +package main + +func foo(x int) int { + return x - x /* NOT OK */ + (x & x) /* NOT OK */ +} + +func bar(b bool, x float32) float32 { + if b { + return (x + x) / 2 // NOT OK + } else { + return (x * x) / 2 // OK + } +} + +const c = 1 + +func baz(b bool) int { + var d = 1 + if b { + return d - 1 // NOT OK + } else { + return c - 1 // OK + } +} + +func main() { + foo(42) +} diff --git a/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.expected b/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.expected new file mode 100644 index 00000000..993110b2 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.expected @@ -0,0 +1,2 @@ +| SelfAssignment.go:12:2:12:16 | ... = ... | This statement assigns $@ to itself. | SelfAssignment.go:12:11:12:16 | height | an expression | +| tst.go:5:2:5:6 | ... = ... | This statement assigns $@ to itself. | tst.go:5:6:5:6 | x | an expression | diff --git a/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.go b/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.go new file mode 100644 index 00000000..ab2e585e --- /dev/null +++ b/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.go @@ -0,0 +1,13 @@ +package main + +type Rect struct { + x, y, width, height int +} + +func (r *Rect) setWidth(width int) { + r.width = width +} + +func (r *Rect) setHeight(height int) { + height = height +} diff --git a/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.qlref b/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.qlref new file mode 100644 index 00000000..3eebdc5d --- /dev/null +++ b/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignment.qlref @@ -0,0 +1 @@ +RedundantCode/SelfAssignment.ql diff --git a/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignmentGood.go b/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignmentGood.go new file mode 100644 index 00000000..68dfc80d --- /dev/null +++ b/ql/test/query-tests/RedundantCode/SelfAssignment/SelfAssignmentGood.go @@ -0,0 +1,5 @@ +package main + +func (r *Rect) setHeightGood(height int) { + r.height = height +} diff --git a/ql/test/query-tests/RedundantCode/SelfAssignment/tst.go b/ql/test/query-tests/RedundantCode/SelfAssignment/tst.go new file mode 100644 index 00000000..31a556ce --- /dev/null +++ b/ql/test/query-tests/RedundantCode/SelfAssignment/tst.go @@ -0,0 +1,6 @@ +package main + +func main() { + x := 42 + x = x // NOT OK +} diff --git a/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.expected b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.expected new file mode 100644 index 00000000..fe0ede2a --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.expected @@ -0,0 +1,4 @@ +| ShiftOutOfRange.go:4:9:4:18 | ...<<... | Shifting a value of 32 bits by 40 always yields either 0 or -1. | +| main.go:4:9:4:14 | ...<<... | Shifting a value of 8 bits by 8 always yields either 0 or -1. | +| main.go:8:9:8:15 | ...>>... | Shifting a value of 32 bits by 33 always yields either 0 or -1. | +| main.go:12:9:12:15 | ...<<... | Shifting a value of (at most) 64 bits by 64 always yields either 0 or -1. | diff --git a/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.go b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.go new file mode 100644 index 00000000..aaa05763 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.go @@ -0,0 +1,7 @@ +package main + +func shift(base int32) int32 { + return base << 40 +} + +var x1 = shift(1) diff --git a/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.qlref b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.qlref new file mode 100644 index 00000000..223322f9 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRange.qlref @@ -0,0 +1 @@ +RedundantCode/ShiftOutOfRange.ql diff --git a/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRangeGood.go b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRangeGood.go new file mode 100644 index 00000000..f43f80c6 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/ShiftOutOfRangeGood.go @@ -0,0 +1,7 @@ +package main + +func shiftGood(base int64) int64 { + return base << 40 +} + +var x2 = shiftGood(1) diff --git a/ql/test/query-tests/RedundantCode/ShiftOutOfRange/main.go b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/main.go new file mode 100644 index 00000000..4afb91d1 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/ShiftOutOfRange/main.go @@ -0,0 +1,28 @@ +package main + +func bad1(x uint8) uint8 { + return x << 8 // NOT OK +} + +func bad2(y int32) int32 { + return y >> 33 // NOT OK +} + +func bad3(z int) int { + return z << 64 // NOT OK +} + +func good1(x uint8) uint8 { + return x << 7 // OK +} + +func good2(y int32) int32 { + return y >> 16 // OK +} + +func good3(z int) int { + return z << 32 // OK +} + +func main() { +} diff --git a/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected b/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected new file mode 100644 index 00000000..458b41d4 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.expected @@ -0,0 +1,8 @@ +| UnreachableStatement.go:5:27:5:29 | increment statement | This statement is unreachable. | +| main.go:9:2:9:14 | expression statement | This statement is unreachable. | +| main.go:14:2:14:14 | expression statement | This statement is unreachable. | +| main.go:18:22:18:34 | expression statement | This statement is unreachable. | +| main.go:26:2:26:14 | expression statement | This statement is unreachable. | +| main.go:45:2:45:14 | expression statement | This statement is unreachable. | +| main.go:51:3:51:15 | expression statement | This statement is unreachable. | +| main.go:53:2:53:14 | expression statement | This statement is unreachable. | diff --git a/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.go b/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.go new file mode 100644 index 00000000..10250238 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.go @@ -0,0 +1,13 @@ +package main + +func mul(xs []int) int { + res := 1 + for i := 0; i < len(xs); i++ { + x := xs[i] + res *= x + if res == 0 { + } + return 0 + } + return res +} diff --git a/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.qlref b/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.qlref new file mode 100644 index 00000000..645ea622 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatement.qlref @@ -0,0 +1 @@ +RedundantCode/UnreachableStatement.ql diff --git a/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatementGood.go b/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatementGood.go new file mode 100644 index 00000000..8d959516 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/UnreachableStatement/UnreachableStatementGood.go @@ -0,0 +1,13 @@ +package main + +func mulGood(xs []int) int { + res := 1 + for i := 0; i < len(xs); i++ { + x := xs[i] + res *= x + if res == 0 { + return 0 + } + } + return res +} diff --git a/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go b/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go new file mode 100644 index 00000000..6ce43219 --- /dev/null +++ b/ql/test/query-tests/RedundantCode/UnreachableStatement/main.go @@ -0,0 +1,104 @@ +package main + +func unreachable() {} + +func reachable() {} + +func test1() { + return + unreachable() // NOT OK +} + +func test2() { + select {} + unreachable() // NOT OK +} + +func test3() { + for i := 0; i < 10; unreachable() { // NOT OK + return + } +} + +func test4() { + for true { + } + unreachable() // NOT OK +} + +func test5(cond bool) { + for true { + if cond { + break + } + } + reachable() +} + +func test6(cond bool) { + for true { + if cond { + continue + } + reachable() + } + unreachable() // NOT OK +} + +func test7(cond bool) { + for true { + continue + unreachable() // NOT OK + } + unreachable() // NOT OK +} + +func test8() { + if true { + return + } + unreachable() // OK: deliberately unreachable +} + +func test9(x int) int { + switch x { + case 0: + return 1 + case 2: + return 3 + default: + return -1 + } + panic("How did we get here?") // OK +} + +func test10(x int) int { + switch x { + case 0: + return 1 + case 2: + return 3 + default: + return 4 + } + return -1 // OK +} + +const debug = false + +var counter int + +func count() { + if debug { + counter++ // OK + } +} + +func test11() { + if false || true { + } + if true && !false { + } +} + +func main() {} diff --git a/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.expected b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.expected new file mode 100644 index 00000000..8cbde9e7 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.expected @@ -0,0 +1,6 @@ +edges +| IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" [string] | IncompleteHostnameRegexp.go:12:41:12:42 | re | +| main.go:12:15:12:39 | `https://www.example.com` [string] | main.go:12:15:12:39 | `https://www.example.com` | +#select +| IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" [string] | IncompleteHostnameRegexp.go:11:11:11:39 | "^((www\|beta).)?example.com/" [string] | IncompleteHostnameRegexp.go:12:41:12:42 | re | This regular expression has an unescaped dot before ')?example.com', so it might match more hosts than expected when used $@. | IncompleteHostnameRegexp.go:12:41:12:42 | re | here | +| main.go:12:15:12:39 | `https://www.example.com` [string] | main.go:12:15:12:39 | `https://www.example.com` [string] | main.go:12:15:12:39 | `https://www.example.com` | This regular expression has an unescaped dot before 'example.com', so it might match more hosts than expected when used $@. | main.go:12:15:12:39 | `https://www.example.com` | here | diff --git a/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.go b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.go new file mode 100644 index 00000000..cccf148c --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.go @@ -0,0 +1,16 @@ +package main + +import ( + "errors" + "regexp" + "net/http" +) + +func checkRedirect(req *http.Request, via []*http.Request) error { + // BAD: the host of `url` may be controlled by an attacker + re := "^((www|beta).)?example.com/" + if matched, _ := regexp.MatchString(re, req.URL.Host); matched { + return nil + } + return errors.New("Invalid redirect") +} diff --git a/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.qlref b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.qlref new file mode 100644 index 00000000..a7920105 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexp.qlref @@ -0,0 +1 @@ +Security/CWE-020/IncompleteHostnameRegexp.ql diff --git a/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexpGood.go b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexpGood.go new file mode 100644 index 00000000..6842395b --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/IncompleteHostnameRegexpGood.go @@ -0,0 +1,16 @@ +package main + +import ( + "errors" + "regexp" + "net/http" +) + +func checkRedirectGood(req *http.Request, via []*http.Request) error { + // GOOD: the host of `url` must be `example.com`, `www.example.com` or `beta.example.com` + re := "^((www|beta)\\.)?example.com/" + if matched, _ := regexp.MatchString(re, req.URL.Host); matched { + return nil + } + return errors.New("Invalid redirect") +} diff --git a/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/main.go b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/main.go new file mode 100644 index 00000000..81123454 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "regexp" +) + +func Match(notARegex string) bool { + return notARegex != "" +} + +func main() { + regexp.Match(`https://www.example.com`, []byte("")) // NOT OK + regexp.Match(`https://www\.example\.com`, []byte("")) // OK +} diff --git a/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.expected b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.expected new file mode 100644 index 00000000..8301590d --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.expected @@ -0,0 +1,10 @@ +| MissingRegexpAnchor.go:11:8:11:38 | "https?://www\\\\.example\\\\.com/" | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. | +| main.go:9:15:9:20 | `^a\|b` | Misleading operator precedence. The subexpression '^a' is anchored, but the other parts of this regular expression are not. | +| main.go:12:15:12:22 | `^a\|b\|c` | Misleading operator precedence. The subexpression '^a' is anchored, but the other parts of this regular expression are not. | +| main.go:18:15:18:22 | `^a\|(b)` | Misleading operator precedence. The subexpression '^a' is anchored, but the other parts of this regular expression are not. | +| main.go:20:15:20:24 | `^(a)\|(b)` | Misleading operator precedence. The subexpression '^(a)' is anchored, but the other parts of this regular expression are not. | +| main.go:22:15:22:20 | `a\|b$` | Misleading operator precedence. The subexpression 'b$' is anchored, but the other parts of this regular expression are not. | +| main.go:25:15:25:22 | `a\|b\|c$` | Misleading operator precedence. The subexpression 'c$' is anchored, but the other parts of this regular expression are not. | +| main.go:31:15:31:22 | `(a)\|b$` | Misleading operator precedence. The subexpression 'b$' is anchored, but the other parts of this regular expression are not. | +| main.go:33:15:33:24 | `(a)\|(b)$` | Misleading operator precedence. The subexpression '(b)$' is anchored, but the other parts of this regular expression are not. | +| main.go:35:15:35:33 | `https?://good.com` | When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it. | diff --git a/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.go b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.go new file mode 100644 index 00000000..60cb9d5b --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.go @@ -0,0 +1,16 @@ +package main + +import ( + "errors" + "net/http" + "regexp" +) + +func checkRedirect2(req *http.Request, via []*http.Request) error { + // BAD: the host of `req.URL` may be controlled by an attacker + re := "https?://www\\.example\\.com/" + if matched, _ := regexp.MatchString(re, req.URL.String()); matched { + return nil + } + return errors.New("Invalid redirect") +} diff --git a/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.qlref b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.qlref new file mode 100644 index 00000000..b03fcd14 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchor.qlref @@ -0,0 +1 @@ +Security/CWE-020/MissingRegexpAnchor.ql diff --git a/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchorGood.go b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchorGood.go new file mode 100644 index 00000000..063e0353 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/MissingRegexpAnchorGood.go @@ -0,0 +1,16 @@ +package main + +import ( + "errors" + "net/http" + "regexp" +) + +func checkRedirect2Good(req *http.Request, via []*http.Request) error { + // GOOD: the host of `req.URL` cannot be controlled by an attacker + re := "^https?://www\\.example\\.com/" + if matched, _ := regexp.MatchString(re, req.URL.String()); matched { + return nil + } + return errors.New("Invalid redirect") +} diff --git a/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/main.go b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/main.go new file mode 100644 index 00000000..d3523549 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-020/MissingRegexpAnchor/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "regexp" +) + +func main() { + regexp.Match(`^a|`, []byte("")) // OK + regexp.Match(`^a|b`, []byte("")) // NOT OK + regexp.Match(`a|^b`, []byte("")) // OK + regexp.Match(`^a|^b`, []byte("")) // OK + regexp.Match(`^a|b|c`, []byte("")) // NOT OK + regexp.Match(`a|^b|c`, []byte("")) // OK + regexp.Match(`a|b|^c`, []byte("")) // OK + regexp.Match(`^a|^b|c`, []byte("")) // OK + + regexp.Match(`(^a)|b`, []byte("")) // OK + regexp.Match(`^a|(b)`, []byte("")) // NOT OK + regexp.Match(`^a|(^b)`, []byte("")) // OK + regexp.Match(`^(a)|(b)`, []byte("")) // NOT OK + + regexp.Match(`a|b$`, []byte("")) // NOT OK + regexp.Match(`a$|b`, []byte("")) // OK + regexp.Match(`a$|b$`, []byte("")) // OK + regexp.Match(`a|b|c$`, []byte("")) // NOT OK + regexp.Match(`a|b$|c`, []byte("")) // OK + regexp.Match(`a$|b|c`, []byte("")) // OK + regexp.Match(`a|b$|c$`, []byte("")) // OK + + regexp.Match(`a|(b$)`, []byte("")) // OK + regexp.Match(`(a)|b$`, []byte("")) // NOT OK + regexp.Match(`(a$)|b$`, []byte("")) // OK + regexp.Match(`(a)|(b)$`, []byte("")) // NOT OK + + regexp.Match(`https?://good.com`, []byte("http://evil.com/?http://good.com")) // NOT OK + regexp.Match(`^https?://good.com`, []byte("http://evil.com/?http://good.com")) // OK +} diff --git a/ql/test/query-tests/Security/CWE-022/TaintedPath.expected b/ql/test/query-tests/Security/CWE-022/TaintedPath.expected new file mode 100644 index 00000000..38bd589e --- /dev/null +++ b/ql/test/query-tests/Security/CWE-022/TaintedPath.expected @@ -0,0 +1,6 @@ +edges +| TaintedPath.go:10:10:10:14 | selection of URL [pointer type] | TaintedPath.go:13:29:13:32 | path | +| TaintedPath.go:10:10:10:14 | selection of URL [pointer type] | TaintedPath.go:17:28:17:61 | call to Join | +#select +| TaintedPath.go:13:29:13:32 | path | TaintedPath.go:10:10:10:14 | selection of URL [pointer type] | TaintedPath.go:13:29:13:32 | path | This path depends on $@. | TaintedPath.go:10:10:10:14 | selection of URL | a user-provided value | +| TaintedPath.go:17:28:17:61 | call to Join | TaintedPath.go:10:10:10:14 | selection of URL [pointer type] | TaintedPath.go:17:28:17:61 | call to Join | This path depends on $@. | TaintedPath.go:10:10:10:14 | selection of URL | a user-provided value | diff --git a/ql/test/query-tests/Security/CWE-022/TaintedPath.go b/ql/test/query-tests/Security/CWE-022/TaintedPath.go new file mode 100644 index 00000000..3b6df910 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-022/TaintedPath.go @@ -0,0 +1,19 @@ +package main + +import ( + "io/ioutil" + "net/http" + "path/filepath" +) + +func handler(w http.ResponseWriter, r *http.Request) { + path := r.URL.Query()["path"][0] + + // BAD: This could read any file on the file system + data, _ := ioutil.ReadFile(path) + w.Write(data) + + // BAD: This could still read any file on the file system + data, _ = ioutil.ReadFile(filepath.Join("/home/user/", path)) + w.Write(data) +} diff --git a/ql/test/query-tests/Security/CWE-022/TaintedPath.qlref b/ql/test/query-tests/Security/CWE-022/TaintedPath.qlref new file mode 100644 index 00000000..53d53cb8 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-022/TaintedPath.qlref @@ -0,0 +1 @@ +Security/CWE-022/TaintedPath.ql diff --git a/ql/test/query-tests/Security/CWE-022/ZipSlip.expected b/ql/test/query-tests/Security/CWE-022/ZipSlip.expected new file mode 100644 index 00000000..4199928f --- /dev/null +++ b/ql/test/query-tests/Security/CWE-022/ZipSlip.expected @@ -0,0 +1,6 @@ +edges +| ZipSlip.go:12:24:12:29 | selection of Name [string] | ZipSlip.go:14:20:14:20 | p | +| tst.go:15:11:15:16 | selection of Name [string] | tst.go:20:20:20:23 | path | +#select +| ZipSlip.go:12:24:12:29 | selection of Name | ZipSlip.go:12:24:12:29 | selection of Name [string] | ZipSlip.go:14:20:14:20 | p | Unsanitized archive entry, which may contain '..', is used in a $@. | ZipSlip.go:14:20:14:20 | p | file system operation | +| tst.go:15:11:15:16 | selection of Name | tst.go:15:11:15:16 | selection of Name [string] | tst.go:20:20:20:23 | path | Unsanitized archive entry, which may contain '..', is used in a $@. | tst.go:20:20:20:23 | path | file system operation | diff --git a/ql/test/query-tests/Security/CWE-022/ZipSlip.go b/ql/test/query-tests/Security/CWE-022/ZipSlip.go new file mode 100644 index 00000000..1628eabb --- /dev/null +++ b/ql/test/query-tests/Security/CWE-022/ZipSlip.go @@ -0,0 +1,16 @@ +package main + +import ( + "archive/zip" + "io/ioutil" + "path/filepath" +) + +func unzip(f string) { + r, _ := zip.OpenReader(f) + for _, f := range r.File { + p, _ := filepath.Abs(f.Name) + // BAD: This could overwrite any file on the file system + ioutil.WriteFile(p, []byte("present"), 0666) + } +} diff --git a/ql/test/query-tests/Security/CWE-022/ZipSlip.qlref b/ql/test/query-tests/Security/CWE-022/ZipSlip.qlref new file mode 100644 index 00000000..0ac6382f --- /dev/null +++ b/ql/test/query-tests/Security/CWE-022/ZipSlip.qlref @@ -0,0 +1 @@ +Security/CWE-022/ZipSlip.ql diff --git a/ql/test/query-tests/Security/CWE-022/ZipSlipGood.go b/ql/test/query-tests/Security/CWE-022/ZipSlipGood.go new file mode 100644 index 00000000..40af79fb --- /dev/null +++ b/ql/test/query-tests/Security/CWE-022/ZipSlipGood.go @@ -0,0 +1,19 @@ +package main + +import ( + "archive/zip" + "io/ioutil" + "path/filepath" + "strings" +) + +func unzipGood(f string) { + r, _ := zip.OpenReader(f) + for _, f := range r.File { + p, _ := filepath.Abs(f.Name) + // GOOD: Check that path does not contain ".." before using it + if !strings.Contains(p, "..") { + ioutil.WriteFile(p, []byte("present"), 0666) + } + } +} diff --git a/ql/test/query-tests/Security/CWE-022/tst.go b/ql/test/query-tests/Security/CWE-022/tst.go new file mode 100644 index 00000000..766f3702 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-022/tst.go @@ -0,0 +1,43 @@ +package main + +import ( + "archive/zip" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" +) + +func unzip2(f string, root string) { + r, _ := zip.OpenReader(f) + for _, f := range r.File { + path := f.Name + relpath, err := filepath.Rel(root, path) + if err == nil { + ioutil.WriteFile(filepath.Join(root, relpath), []byte("present"), 0666) // OK + } + ioutil.WriteFile(path, []byte("present"), 0666) // NOT OK + if containedIn(path, root) { + ioutil.WriteFile(path, []byte("present"), 0666) // OK + } + if ok, _ := regexp.MatchString("^[a-z]*$", path); ok { + ioutil.WriteFile(path, []byte("present"), 0666) // OK + } + if !strings.HasPrefix(path, filepath.Clean(root)+string(os.PathSeparator)) { + panic("Invalid path!") + } + ioutil.WriteFile(path, []byte("present"), 0666) // OK + if containedIn(f.Name, root) { + ioutil.WriteFile(f.Name, []byte("present"), 0666) // OK + } + } +} + +func containedIn(f string, root string) bool { + _, err := filepath.Rel(root, f) + if err == nil { + return true + } + return false +} diff --git a/ql/test/query-tests/Security/CWE-078/CommandInjection.expected b/ql/test/query-tests/Security/CWE-078/CommandInjection.expected new file mode 100644 index 00000000..177a0980 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-078/CommandInjection.expected @@ -0,0 +1,4 @@ +edges +| CommandInjection.go:9:13:9:19 | selection of URL [pointer type] | CommandInjection.go:10:22:10:28 | cmdName | +#select +| CommandInjection.go:10:22:10:28 | cmdName | CommandInjection.go:9:13:9:19 | selection of URL [pointer type] | CommandInjection.go:10:22:10:28 | cmdName | This command depends on $@. | CommandInjection.go:9:13:9:19 | selection of URL | a user-provided value | diff --git a/ql/test/query-tests/Security/CWE-078/CommandInjection.go b/ql/test/query-tests/Security/CWE-078/CommandInjection.go new file mode 100644 index 00000000..ff046f24 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-078/CommandInjection.go @@ -0,0 +1,12 @@ +package main + +import ( + "net/http" + "os/exec" +) + +func handler(req *http.Request) { + cmdName := req.URL.Query()["cmd"][0] + cmd := exec.Command(cmdName) + cmd.Run() +} diff --git a/ql/test/query-tests/Security/CWE-078/CommandInjection.qlref b/ql/test/query-tests/Security/CWE-078/CommandInjection.qlref new file mode 100644 index 00000000..e38b88f2 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-078/CommandInjection.qlref @@ -0,0 +1 @@ +Security/CWE-078/CommandInjection.ql diff --git a/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected new file mode 100644 index 00000000..1d25218c --- /dev/null +++ b/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -0,0 +1,6 @@ +edges +| ReflectedXss.go:11:15:11:20 | selection of Form [Values] | ReflectedXss.go:14:39:14:46 | username | +| contenttype.go:10:11:10:16 | selection of Form [Values] | contenttype.go:16:11:16:22 | type conversion | +#select +| ReflectedXss.go:14:39:14:46 | username | ReflectedXss.go:11:15:11:20 | selection of Form [Values] | ReflectedXss.go:14:39:14:46 | username | Cross-site scripting vulnerability due to $@. | ReflectedXss.go:11:15:11:20 | selection of Form | user-provided value | +| contenttype.go:16:11:16:22 | type conversion | contenttype.go:10:11:10:16 | selection of Form [Values] | contenttype.go:16:11:16:22 | type conversion | Cross-site scripting vulnerability due to $@. | contenttype.go:10:11:10:16 | selection of Form | user-provided value | diff --git a/ql/test/query-tests/Security/CWE-079/ReflectedXss.go b/ql/test/query-tests/Security/CWE-079/ReflectedXss.go new file mode 100644 index 00000000..3f976dc7 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-079/ReflectedXss.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "net/http" +) + +func serve() { + http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + username := r.Form.Get("username") + if !isValidUsername(username) { + // BAD: a request parameter is incorporated without validation into the response + fmt.Fprintf(w, "Unknown user: %q", username) + } else { + // TODO: do something exciting + } + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref b/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref new file mode 100644 index 00000000..e0efe102 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref @@ -0,0 +1 @@ +Security/CWE-079/ReflectedXss.ql diff --git a/ql/test/query-tests/Security/CWE-079/ReflectedXssGood.go b/ql/test/query-tests/Security/CWE-079/ReflectedXssGood.go new file mode 100644 index 00000000..19408bcf --- /dev/null +++ b/ql/test/query-tests/Security/CWE-079/ReflectedXssGood.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "html" + "net/http" +) + +func serve1() { + http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + username := r.Form.Get("username") + if !isValidUsername(username) { + // BAD: a request parameter is incorporated without validation into the response + fmt.Fprintf(w, "Unknown user: %q", html.EscapeString(username)) + } else { + // TODO: do something exciting + } + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/test/query-tests/Security/CWE-079/contenttype.go b/ql/test/query-tests/Security/CWE-079/contenttype.go new file mode 100644 index 00000000..97b1ce9f --- /dev/null +++ b/ql/test/query-tests/Security/CWE-079/contenttype.go @@ -0,0 +1,31 @@ +package main + +import ( + "net/http" +) + +func serve2() { + http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + data := r.Form.Get("data") + + // Not OK; direct flow from request body to output. + // The response Content-Type header is derived from a call to + // `http.DetectContentType`, which can be easily manipulated into returning + // `text/html` for XSS. + w.Write([]byte(data)) + }) + http.ListenAndServe(":80", nil) +} + +func serve3() { + http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + data := r.Form.Get("data") + + w.Header().Set("Content-Type", "text/plain") + + w.Write([]byte(data)) // OK; no script can be executed from a `text/plain` context. + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/test/query-tests/Security/CWE-079/util.go b/ql/test/query-tests/Security/CWE-079/util.go new file mode 100644 index 00000000..07bf42b1 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-079/util.go @@ -0,0 +1,5 @@ +package main + +func isValidUsername(s string) bool { + return false +} diff --git a/ql/test/query-tests/Security/CWE-089/SqlInjection.expected b/ql/test/query-tests/Security/CWE-089/SqlInjection.expected new file mode 100644 index 00000000..b45e18da --- /dev/null +++ b/ql/test/query-tests/Security/CWE-089/SqlInjection.expected @@ -0,0 +1,6 @@ +edges +| SqlInjection.go:11:3:11:9 | selection of URL [pointer type] | SqlInjection.go:12:11:12:11 | q | +| main.go:9:11:9:16 | selection of Form [Values] | main.go:9:11:9:28 | index expression | +#select +| SqlInjection.go:12:11:12:11 | q | SqlInjection.go:11:3:11:9 | selection of URL [pointer type] | SqlInjection.go:12:11:12:11 | q | This query depends on $@. | SqlInjection.go:11:3:11:9 | selection of URL | a user-provided value | +| main.go:9:11:9:28 | index expression | main.go:9:11:9:16 | selection of Form [Values] | main.go:9:11:9:28 | index expression | This query depends on $@. | main.go:9:11:9:16 | selection of Form | a user-provided value | diff --git a/ql/test/query-tests/Security/CWE-089/SqlInjection.go b/ql/test/query-tests/Security/CWE-089/SqlInjection.go new file mode 100644 index 00000000..0df976d9 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-089/SqlInjection.go @@ -0,0 +1,13 @@ +package main + +import ( + "database/sql" + "fmt" + "net/http" +) + +func handler(db *sql.DB, req *http.Request) { + q := fmt.Sprintf("SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='%s' ORDER BY PRICE", + req.URL.Query()["category"]) + db.Query(q) +} diff --git a/ql/test/query-tests/Security/CWE-089/SqlInjection.qlref b/ql/test/query-tests/Security/CWE-089/SqlInjection.qlref new file mode 100644 index 00000000..d1d02cbe --- /dev/null +++ b/ql/test/query-tests/Security/CWE-089/SqlInjection.qlref @@ -0,0 +1 @@ +Security/CWE-089/SqlInjection.ql diff --git a/ql/test/query-tests/Security/CWE-089/SqlInjectionGood.go b/ql/test/query-tests/Security/CWE-089/SqlInjectionGood.go new file mode 100644 index 00000000..8c9ea861 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-089/SqlInjectionGood.go @@ -0,0 +1,11 @@ +package main + +import ( + "database/sql" + "net/http" +) + +func handlerGood(db *sql.DB, req *http.Request) { + q := "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='?' ORDER BY PRICE" + db.Query(q, req.URL.Query()["category"]) +} diff --git a/ql/test/query-tests/Security/CWE-089/main.go b/ql/test/query-tests/Security/CWE-089/main.go new file mode 100644 index 00000000..3aff9b05 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-089/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "database/sql" + "net/http" +) + +func test(db *sql.DB, r *http.Request) { + db.Query(r.Form["query"][0]) // NOT OK +} + +func main() {} diff --git a/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected b/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected new file mode 100644 index 00000000..4b565ae2 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/CleartextLogging.expected @@ -0,0 +1,54 @@ +edges +| passwords.go:8:12:8:12 | definition of x [string] | passwords.go:9:14:9:14 | x | +| passwords.go:25:14:25:21 | password [string] | passwords.go:25:14:25:21 | password | +| passwords.go:26:14:26:23 | selection of password [string] | passwords.go:26:14:26:23 | selection of password | +| passwords.go:27:14:27:26 | call to getPassword [string] | passwords.go:27:14:27:26 | call to getPassword | +| passwords.go:28:14:28:28 | call to getPassword [string] | passwords.go:28:14:28:28 | call to getPassword | +| passwords.go:30:8:30:15 | password [string] | passwords.go:8:12:8:12 | definition of x [string] | +| passwords.go:32:12:32:19 | password [string] | passwords.go:32:12:32:19 | password | +| passwords.go:34:28:34:35 | password [string] | passwords.go:34:14:34:35 | ...+... | +| passwords.go:36:10:38:2 | composite literal [passStruct] | passwords.go:39:14:39:17 | obj1 | +| passwords.go:42:6:42:13 | password [string] | passwords.go:44:14:44:17 | obj2 | +| passwords.go:48:11:48:18 | password [string] | passwords.go:47:14:47:17 | obj3 | +| passwords.go:51:14:51:27 | fixed_password [string] | passwords.go:51:14:51:27 | fixed_password | +| passwords.go:85:19:87:2 | composite literal [passSetStruct] | passwords.go:88:14:88:26 | utilityObject | +| passwords.go:90:12:90:19 | password [string] | passwords.go:91:23:91:28 | secret | +| passwords.go:101:33:101:40 | password [string] | passwords.go:101:15:101:40 | ...+... | +| passwords.go:107:34:107:41 | password [string] | passwords.go:107:16:107:41 | ...+... | +| passwords.go:112:33:112:40 | password [string] | passwords.go:112:15:112:40 | ...+... | +| passwords.go:116:28:116:36 | password1 [stringable] | passwords.go:116:14:116:45 | ...+... | +| passwords.go:118:12:123:2 | composite literal [Config] | passwords.go:125:14:125:19 | config | +| passwords.go:118:12:123:2 | composite literal [x, ... (1)] | passwords.go:126:14:126:19 | config [x, ... (1)] | +| passwords.go:118:12:123:2 | composite literal [y, ... (1)] | passwords.go:127:14:127:19 | config [y, ... (1)] | +| passwords.go:121:13:121:20 | password [string] | passwords.go:118:12:123:2 | composite literal [x, ... (1)] | +| passwords.go:121:13:121:20 | password [string] | passwords.go:125:14:125:19 | config | +| passwords.go:122:13:122:25 | call to getPassword [string] | passwords.go:118:12:123:2 | composite literal [y, ... (1)] | +| passwords.go:122:13:122:25 | call to getPassword [string] | passwords.go:125:14:125:19 | config | +| passwords.go:126:14:126:19 | config [x, ... (1)] | passwords.go:126:14:126:21 | selection of x | +| passwords.go:127:14:127:19 | config [y, ... (1)] | passwords.go:127:14:127:21 | selection of y | +| util.go:14:9:14:18 | selection of password [string] | passwords.go:28:14:28:28 | call to getPassword | +| util.go:14:9:14:18 | selection of password [string] | passwords.go:28:14:28:28 | call to getPassword [string] | +#select +| passwords.go:9:14:9:14 | x | passwords.go:30:8:30:15 | password [string] | passwords.go:9:14:9:14 | x | Sensitive data returned by $@ is logged here. | passwords.go:30:8:30:15 | password | an access to password | +| passwords.go:25:14:25:21 | password | passwords.go:25:14:25:21 | password [string] | passwords.go:25:14:25:21 | password | Sensitive data returned by $@ is logged here. | passwords.go:25:14:25:21 | password | an access to password | +| passwords.go:26:14:26:23 | selection of password | passwords.go:26:14:26:23 | selection of password [string] | passwords.go:26:14:26:23 | selection of password | Sensitive data returned by $@ is logged here. | passwords.go:26:14:26:23 | selection of password | an access to password | +| passwords.go:27:14:27:26 | call to getPassword | passwords.go:27:14:27:26 | call to getPassword [string] | passwords.go:27:14:27:26 | call to getPassword | Sensitive data returned by $@ is logged here. | passwords.go:27:14:27:26 | call to getPassword | a call to getPassword | +| passwords.go:28:14:28:28 | call to getPassword | passwords.go:28:14:28:28 | call to getPassword [string] | passwords.go:28:14:28:28 | call to getPassword | Sensitive data returned by $@ is logged here. | passwords.go:28:14:28:28 | call to getPassword | a call to getPassword | +| passwords.go:28:14:28:28 | call to getPassword | util.go:14:9:14:18 | selection of password [string] | passwords.go:28:14:28:28 | call to getPassword | Sensitive data returned by $@ is logged here. | util.go:14:9:14:18 | selection of password | an access to password | +| passwords.go:32:12:32:19 | password | passwords.go:32:12:32:19 | password [string] | passwords.go:32:12:32:19 | password | Sensitive data returned by $@ is logged here. | passwords.go:32:12:32:19 | password | an access to password | +| passwords.go:34:14:34:35 | ...+... | passwords.go:34:28:34:35 | password [string] | passwords.go:34:14:34:35 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:34:28:34:35 | password | an access to password | +| passwords.go:39:14:39:17 | obj1 | passwords.go:36:10:38:2 | composite literal [passStruct] | passwords.go:39:14:39:17 | obj1 | Sensitive data returned by $@ is logged here. | passwords.go:36:10:38:2 | composite literal | an access to password | +| passwords.go:44:14:44:17 | obj2 | passwords.go:42:6:42:13 | password [string] | passwords.go:44:14:44:17 | obj2 | Sensitive data returned by $@ is logged here. | passwords.go:42:6:42:13 | password | an access to password | +| passwords.go:47:14:47:17 | obj3 | passwords.go:48:11:48:18 | password [string] | passwords.go:47:14:47:17 | obj3 | Sensitive data returned by $@ is logged here. | passwords.go:48:11:48:18 | password | an access to password | +| passwords.go:51:14:51:27 | fixed_password | passwords.go:51:14:51:27 | fixed_password [string] | passwords.go:51:14:51:27 | fixed_password | Sensitive data returned by $@ is logged here. | passwords.go:51:14:51:27 | fixed_password | an access to fixed_password | +| passwords.go:88:14:88:26 | utilityObject | passwords.go:85:19:87:2 | composite literal [passSetStruct] | passwords.go:88:14:88:26 | utilityObject | Sensitive data returned by $@ is logged here. | passwords.go:85:19:87:2 | composite literal | an access to passwordSet | +| passwords.go:91:23:91:28 | secret | passwords.go:90:12:90:19 | password [string] | passwords.go:91:23:91:28 | secret | Sensitive data returned by $@ is logged here. | passwords.go:90:12:90:19 | password | an access to password | +| passwords.go:101:15:101:40 | ...+... | passwords.go:101:33:101:40 | password [string] | passwords.go:101:15:101:40 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:101:33:101:40 | password | an access to password | +| passwords.go:107:16:107:41 | ...+... | passwords.go:107:34:107:41 | password [string] | passwords.go:107:16:107:41 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:107:34:107:41 | password | an access to password | +| passwords.go:112:15:112:40 | ...+... | passwords.go:112:33:112:40 | password [string] | passwords.go:112:15:112:40 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:112:33:112:40 | password | an access to password | +| passwords.go:116:14:116:45 | ...+... | passwords.go:116:28:116:36 | password1 [stringable] | passwords.go:116:14:116:45 | ...+... | Sensitive data returned by $@ is logged here. | passwords.go:116:28:116:36 | password1 | an access to password1 | +| passwords.go:125:14:125:19 | config | passwords.go:118:12:123:2 | composite literal [Config] | passwords.go:125:14:125:19 | config | Sensitive data returned by $@ is logged here. | passwords.go:118:12:123:2 | composite literal | an access to password | +| passwords.go:125:14:125:19 | config | passwords.go:121:13:121:20 | password [string] | passwords.go:125:14:125:19 | config | Sensitive data returned by $@ is logged here. | passwords.go:121:13:121:20 | password | an access to password | +| passwords.go:125:14:125:19 | config | passwords.go:122:13:122:25 | call to getPassword [string] | passwords.go:125:14:125:19 | config | Sensitive data returned by $@ is logged here. | passwords.go:122:13:122:25 | call to getPassword | a call to getPassword | +| passwords.go:126:14:126:21 | selection of x | passwords.go:121:13:121:20 | password [string] | passwords.go:126:14:126:21 | selection of x | Sensitive data returned by $@ is logged here. | passwords.go:121:13:121:20 | password | an access to password | +| passwords.go:127:14:127:21 | selection of y | passwords.go:122:13:122:25 | call to getPassword [string] | passwords.go:127:14:127:21 | selection of y | Sensitive data returned by $@ is logged here. | passwords.go:122:13:122:25 | call to getPassword | a call to getPassword | diff --git a/ql/test/query-tests/Security/CWE-312/CleartextLogging.go b/ql/test/query-tests/Security/CWE-312/CleartextLogging.go new file mode 100644 index 00000000..29f6a6ef --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/CleartextLogging.go @@ -0,0 +1,17 @@ +package main + +import ( + "log" + "net/http" +) + +func serve() { + http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + user := r.Form.Get("user") + pw := r.Form.Get("password") + + log.Printf("Registering new user %s with password %s.\n", user, pw) + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/test/query-tests/Security/CWE-312/CleartextLogging.qlref b/ql/test/query-tests/Security/CWE-312/CleartextLogging.qlref new file mode 100644 index 00000000..21eebbd0 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/CleartextLogging.qlref @@ -0,0 +1 @@ +Security/CWE-312/CleartextLogging.ql diff --git a/ql/test/query-tests/Security/CWE-312/CleartextLoggingGood.go b/ql/test/query-tests/Security/CWE-312/CleartextLoggingGood.go new file mode 100644 index 00000000..3b4e1937 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/CleartextLoggingGood.go @@ -0,0 +1,19 @@ +// +build ignore + +package main + +import ( + "log" + "net/http" +) + +func serve1() { + http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + user := r.Form.Get("user") + pw := r.Form.Get("password") + + log.Printf("Registering new user %s.\n", user) + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/test/query-tests/Security/CWE-312/CleartextStorage.go b/ql/test/query-tests/Security/CWE-312/CleartextStorage.go new file mode 100644 index 00000000..df59e07a --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/CleartextStorage.go @@ -0,0 +1,21 @@ +package main + +import ( + "net/http" +) + +func serve2() { + http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + user := r.Form.Get("user") + pw := r.Form.Get("password") + + userdb.Store(user, pw) + + var pwCookie http.Cookie + pwCookie.Name = "password" + pwCookie.Value = pw + http.SetCookie(w, &pwCookie) + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/test/query-tests/Security/CWE-312/CleartextStorageGood.go b/ql/test/query-tests/Security/CWE-312/CleartextStorageGood.go new file mode 100644 index 00000000..5fc7a478 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/CleartextStorageGood.go @@ -0,0 +1,62 @@ +// +build ignore + +package main + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "log" + "net/http" + + "golang.org/x/crypto/scrypt" +) + +var tokens = make(map[string]string) + +func saltAndHash(pw string) ([]byte, []byte) { + salt := make([]byte, 64) + _, err := io.ReadFull(rand.Reader, salt) + if err != nil { + log.Fatal(err) + } + + hash, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 64) + + return hash, salt +} + +func genToken(user string) { + res := make([]byte, 32) + _, err := io.ReadFull(rand.Reader, salt) + if err != nil { + log.Fatal(err) + } + + base64, err := base64.EncodeToString(res) + if err != nil { + log.Fatal(err) + } + + return base64 +} + +func serve1() { + http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + user := r.Form.Get("user") + pw := r.Form.Get("password") + + log.Printf("Registering new user %s.\n", user) + + hash, salt = saltAndHash(pw) + + userdb.Store(user, hash, salt) + + var tokenCookie Cookie + tokenCookie.Name = "auth" + tokenCookie.Value = genToken(user) + http.SetCookie(w, encrypt(pwCookie)) + }) + http.ListenAndServe(":80", nil) +} diff --git a/ql/test/query-tests/Security/CWE-312/go.mod b/ql/test/query-tests/Security/CWE-312/go.mod new file mode 100644 index 00000000..98d9f66c --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/go.mod @@ -0,0 +1,3 @@ +module main + +go 1.13 diff --git a/ql/test/query-tests/Security/CWE-312/main.go b/ql/test/query-tests/Security/CWE-312/main.go new file mode 100644 index 00000000..4500b805 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/main.go @@ -0,0 +1,27 @@ +// +build ignore + +package main + +import ( + "fmt" + "github.com/google/glog" + "github.com/sirupsen/logrus" + "log" +) + +func main() { + password := "P4ssw0rd" + + log.Println(password) + + glog.Info(password) + logrus.Warning(password) + + fields := make(logrus.Fields) + fields["pass"] = password + entry := logrus.WithFields(fields) + entry.Errorf("") + + entry = logrus.WithField("pass", password) + entry.Panic("") +} diff --git a/ql/test/query-tests/Security/CWE-312/passwords.go b/ql/test/query-tests/Security/CWE-312/passwords.go new file mode 100644 index 00000000..b0dfb034 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/passwords.go @@ -0,0 +1,128 @@ +package main + +import ( + "log" + "os" +) + +func myLog(x string) { + log.Println(x) // NOT OK +} + +func redact(kind, value string) string { + if value != "" && kind == "password" { + return "********" + } + return value +} + +func test() { + name := "user" + password := "P@ssw0rd" + x := "aaaaa" + var o passStruct + + log.Println(password) // NOT OK + log.Println(o.password) // NOT OK + log.Println(getPassword()) // NOT OK + log.Println(o.getPassword()) // NOT OK + + myLog(password) + + log.Panic(password) // NOT OK + + log.Println(name + ", " + password) // NOT OK + + obj1 := passStruct{ + password: x, + } + log.Println(obj1) // NOT OK + + obj2 := xStruct{ + x: password, + } + log.Println(obj2) // NOT OK + + var obj3 xStruct + log.Println(obj3) // caught because of the below line + obj3.x = password // NOT OK + + fixed_password := "123" + log.Println(fixed_password) // Probably OK, but caught + + log.Println(IncorrectPasswordError) // OK + + var obj hashedStruct + log.Println(obj.hashed_password) // OK + var login encryptedStruct + log.Println(login.encryptedPassword) // OK + var HTML5QQ encodedStruct + log.Println(HTML5QQ.encodedPassword) // OK + + pw := "arstneioarstneio" + log.Println(passStruct{password: crypt(pw)}) // OK + actually_secure_password := crypt(password) // OK + log.Println(actually_secure_password) // OK + + var user1 cryptedStruct + user1.cryptedPassword = x + log.Println(user1) // OK + + var user2 passStruct + user2.password = hash() + log.Println(user2) // OK + + actually_ok_password_1 := hash() + log.Println(actually_ok_password_1) // OK + hashed2 := actually_ok_password_2() + log.Println(hashed2) // OK + + passwordMD5 := "0d599f0ec05c3bda8c3b8a68c32a1b47" + log.Println(passwordMD5) // OK + password_sha := "5e03aad954f62810c0fdf16cb0dfba86bda486a53912fb7c894f96eaf8008aee" + log.Println(password_sha) // OK + + utilityObject := passSetStruct{ + passwordSet: make(map[string]bool), + } + log.Println(utilityObject) // NOT OK + + secret := password + log.Printf("pw: %s", secret) // NOT OK + + log.Println("Password is: " + redact("password", password)) + + var t testable + var y int + + if t.test(y) { + f() + // ... + log.Println("Password is: " + password) // NOT OK + // ... + } + + if t.test(y) { + if f() { + log.Println("Password is: " + password) // NOT OK + } + } + + if os.Getenv("APP_ENV") != "production" { + log.Println("Password is: " + password) // OK, but still flagged + } + + var password1 stringable = stringable{"arstneio"} + log.Println(name + ", " + password1.String()) // NOT OK + + config := Config{ + password: x, + hostname: "tarski", + x: password, + y: getPassword(), + } + log.Println(config.hostname) // OK + log.Println(config) // NOT OK + log.Println(config.x) // NOT OK + log.Println(config.y) // NOT OK +} diff --git a/ql/test/query-tests/Security/CWE-312/server1.go b/ql/test/query-tests/Security/CWE-312/server1.go new file mode 100644 index 00000000..f7fd4b1b --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/server1.go @@ -0,0 +1,32 @@ +package main + +import ( + "log" + "net/http" +) + +func serve1() { + http.HandleFunc("/some/path", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + logStrs(r.Form.Get("password")) + + vals := r.URL.Query() + logStrs(vals["password"]...) + + var user3 = passStruct{ + password: encryptLib.encryptPassword(r.Form.Get("password")), + } + log.Println(user3) // OK + + temp := encryptedStruct{encryptedPassword: r.Form.Get("password")} + log.Println(temp.encryptedPassword) // OK + }) +} + +func logStrs(x ...string) { + s := make([]interface{}, len(x)) + for i, v := range x { + s[i] = v + } + log.Println(s...) +} diff --git a/ql/test/query-tests/Security/CWE-312/util.go b/ql/test/query-tests/Security/CWE-312/util.go new file mode 100644 index 00000000..22af3af9 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-312/util.go @@ -0,0 +1,69 @@ +package main + +var userdb *UserDB = &UserDB{} + +type UserDB struct{} + +func (user *UserDB) Store(args ...interface{}) {} + +type passStruct struct { + password string +} + +func (p passStruct) getPassword() string { + return p.password +} + +type xStruct struct { + x string +} + +type hashedStruct struct { + hashed_password string +} + +type cryptedStruct struct { + cryptedPassword string +} + +type encryptedStruct struct { + encryptedPassword string +} + +type encodedStruct struct { + encodedPassword string +} + +type passSetStruct struct { + passwordSet map[string]bool +} + +type Config struct { + password, hostname, x, y string +} + +func getPassword() string { return "" } + +const IncorrectPasswordError = iota + +func crypt(pass string) string { return "" } + +func hash(pass ...string) string { return "" } + +type lib struct{} + +var encryptLib lib + +func (l lib) encryptPassword(pass string) string { return "" } + +func actually_ok_password_2() string { return "" } + +type testable struct{} + +func (t testable) test(o interface{}) bool { return true } + +func f() bool { return false } + +type stringable struct{ string } + +func (s stringable) String() string { return s.string } diff --git a/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.expected b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.expected new file mode 100644 index 00000000..d2b8710b --- /dev/null +++ b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.expected @@ -0,0 +1,16 @@ +edges +| OpenUrlRedirect.go:10:23:10:28 | selection of Form [Values] | OpenUrlRedirect.go:10:23:10:42 | call to Get | +| stdlib.go:12:13:12:18 | selection of Form [Values] | stdlib.go:14:30:14:35 | target | +| stdlib.go:21:13:21:18 | selection of Form [Values] | stdlib.go:23:30:23:35 | target | +| stdlib.go:30:13:30:18 | selection of Form [Values] | stdlib.go:34:30:34:39 | ...+... | +| stdlib.go:43:13:43:18 | selection of Form [Values] | stdlib.go:45:23:45:28 | target | +| stdlib.go:63:13:63:18 | selection of Form [Values] | stdlib.go:66:23:66:40 | ...+... | +| stdlib.go:88:13:88:18 | selection of Form [Values] | stdlib.go:91:23:91:28 | target | +#select +| OpenUrlRedirect.go:10:23:10:42 | call to Get | OpenUrlRedirect.go:10:23:10:28 | selection of Form [Values] | OpenUrlRedirect.go:10:23:10:42 | call to Get | Untrusted URL redirection due to $@. | OpenUrlRedirect.go:10:23:10:28 | selection of Form | user-provided value | +| stdlib.go:14:30:14:35 | target | stdlib.go:12:13:12:18 | selection of Form [Values] | stdlib.go:14:30:14:35 | target | Untrusted URL redirection due to $@. | stdlib.go:12:13:12:18 | selection of Form | user-provided value | +| stdlib.go:23:30:23:35 | target | stdlib.go:21:13:21:18 | selection of Form [Values] | stdlib.go:23:30:23:35 | target | Untrusted URL redirection due to $@. | stdlib.go:21:13:21:18 | selection of Form | user-provided value | +| stdlib.go:34:30:34:39 | ...+... | stdlib.go:30:13:30:18 | selection of Form [Values] | stdlib.go:34:30:34:39 | ...+... | Untrusted URL redirection due to $@. | stdlib.go:30:13:30:18 | selection of Form | user-provided value | +| stdlib.go:45:23:45:28 | target | stdlib.go:43:13:43:18 | selection of Form [Values] | stdlib.go:45:23:45:28 | target | Untrusted URL redirection due to $@. | stdlib.go:43:13:43:18 | selection of Form | user-provided value | +| stdlib.go:66:23:66:40 | ...+... | stdlib.go:63:13:63:18 | selection of Form [Values] | stdlib.go:66:23:66:40 | ...+... | Untrusted URL redirection due to $@. | stdlib.go:63:13:63:18 | selection of Form | user-provided value | +| stdlib.go:91:23:91:28 | target | stdlib.go:88:13:88:18 | selection of Form [Values] | stdlib.go:91:23:91:28 | target | Untrusted URL redirection due to $@. | stdlib.go:88:13:88:18 | selection of Form | user-provided value | diff --git a/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.go b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.go new file mode 100644 index 00000000..606b5d43 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.go @@ -0,0 +1,12 @@ +package main + +import ( + "net/http" +) + +func serve() { + http.HandleFunc("/redir", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + http.Redirect(w, r, r.Form.Get("target"), 302) + }) +} diff --git a/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.qlref b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.qlref new file mode 100644 index 00000000..0f1a7477 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirect.qlref @@ -0,0 +1 @@ +Security/CWE-601/OpenUrlRedirect.ql diff --git a/ql/test/query-tests/Security/CWE-601/OpenUrlRedirectGood.go b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirectGood.go new file mode 100644 index 00000000..7ff36863 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-601/OpenUrlRedirectGood.go @@ -0,0 +1,22 @@ +package main + +import ( + "net/http" + "net/url" +) + +func serve1() { + http.HandleFunc("/redir", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + target, err := url.Parse(r.Form.Get("target")) + if err != nil { + // ... + } + if target.Hostname() == "semmle.com" { + // GOOD: checking hostname + http.Redirect(w, r, target.String(), 302) + } else { + w.WriteHeader(400) + } + }) +} diff --git a/ql/test/query-tests/Security/CWE-601/stdlib.go b/ql/test/query-tests/Security/CWE-601/stdlib.go new file mode 100644 index 00000000..e9a0f18c --- /dev/null +++ b/ql/test/query-tests/Security/CWE-601/stdlib.go @@ -0,0 +1,107 @@ +package main + +import ( + "net/http" + "regexp" +) + +func serveStdlib() { + http.HandleFunc("/ex", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + // BAD: a request parameter is incorporated without validation into a URL redirect + w.Header().Set("Location", target) + w.WriteHeader(302) + }) + + http.HandleFunc("/ex1", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + // Probably OK because the status is set to 500, but we catch it anyway + w.Header().Set("Location", target) + w.WriteHeader(500) + }) + + http.HandleFunc("/ex2", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + // GOOD: local redirects are unproblematic + w.Header().Set("Location", "/local"+target) + // BAD: this could be a non-local redirect + w.Header().Set("Location", "/"+target) + // GOOD: localhost redirects are unproblematic + w.Header().Set("Location", "//localhost/"+target) + w.WriteHeader(302) + }) + + http.HandleFunc("/ex3", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + // BAD: using the utility function + http.Redirect(w, r, target, 301) + }) + + http.HandleFunc("/ex4", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + // GOOD: comparison against known URLs + if target == "semmle.com" { + http.Redirect(w, r, target, 301) + } else { + w.WriteHeader(400) + } + }) + + http.HandleFunc("/ex5", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + me := "me" + // BAD: may be a global redirection + http.Redirect(w, r, target+"?from="+me, 301) + }) + + http.HandleFunc("/ex6", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + // GOOD: request parameter is embedded in query string + http.Redirect(w, r, someUrl()+"?target="+target, 301) + }) + + http.HandleFunc("/ex7", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + // GOOD: request parameter is embedded in hash + http.Redirect(w, r, someUrl()+(HASH+target), 302) + }) + + http.HandleFunc("/ex7", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + target += "/index.html" + // BAD + http.Redirect(w, r, target, 302) + }) + + http.HandleFunc("/ex7", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + target := r.Form.Get("target") + // GOOD: request parameter is checked against a regexp + if ok, _ := regexp.MatchString("", target); ok { + http.Redirect(w, r, target, 302) + } else { + w.WriteHeader(400) + } + }) + + http.ListenAndServe(":80", nil) +} diff --git a/ql/test/query-tests/Security/CWE-601/util.go b/ql/test/query-tests/Security/CWE-601/util.go new file mode 100644 index 00000000..5c074166 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-601/util.go @@ -0,0 +1,5 @@ +package main + +const HASH = "#" + +func someUrl() string { return "semmle.com" } diff --git a/ql/test/query-tests/Security/CWE-798/AlertSuppressionExample.go b/ql/test/query-tests/Security/CWE-798/AlertSuppressionExample.go new file mode 100644 index 00000000..c6cd3693 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-798/AlertSuppressionExample.go @@ -0,0 +1,15 @@ +package main + +import "testing" + +func login(user, password string) bool { + return true +} + +func TestLogin(t *testing.T) { + user := "testuser" + password := "horsebatterystaplecorrect" // lgtm[go/hardcoded-credentials] + if !login(user, password) { + t.Errorf("Login test failed.") + } +} diff --git a/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.expected b/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.expected new file mode 100644 index 00000000..aa1815f1 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.expected @@ -0,0 +1,7 @@ +| AlertSuppressionExample.go:11:14:11:40 | "horsebatterystaplecorrect" | Hard-coded $@. | AlertSuppressionExample.go:11:14:11:40 | "horsebatterystaplecorrect" | password | +| HardcodedCredentials.go:10:13:10:28 | "secretpassword" | Hard-coded $@. | HardcodedCredentials.go:10:13:10:28 | "secretpassword" | password | +| main.go:6:17:6:26 | "passw0rd" | Hard-coded $@. | main.go:6:17:6:26 | "passw0rd" | password | +| main.go:13:1:27:30 | `-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQC/tzdtXKXcX6F3v3hR6+uYyZpIeXhhLflJkY2eILLQfAnwKlT5\nxIHW5QZcHQV9sCyZ8qSdPGif7PwgMbButMbByiZhCSugUFb6vjVqoktmslYF4LKH\niDgvmlwuJW0TvynxBLzDCwrRP+gpRT8wuAortWAx/03POTw7Mzi2cIPNsQIDAQAB\nAoGAMHCrqY9CPTdQhgAz94cDpTwzJmLCvtMt7J/BR5X9eF4O6MbZZ652HAUMIVQX\n4hUUf+VmIHB2AwqO/ddwO9ijaz04BslOSy/iYevHGlH65q4587NSlFWjvILMIQCM\nGBjfzJIxlLHVhjc2cFnyAE5YWjF/OMnJN0OhP9pxmCP/iM0CQQDxmQndQLdnV7+6\n8SvBHE8bg1LE8/BzTt68U3aWwiBjrHMFgzr//7Za4VF7h4ilFgmbh0F3sYz+C8iO\n0JrBRPeLAkEAyyTwnv/pgqTS/wuxIHUxRBpbdk3YvILAthNrGQg5uzA7eSeFu7Mv\nGtEkXsaqCDbdehgarFfNN8PB6OMRIbsXMwJBAOjhH8UJ0L/osYO9XPO0GfznRS1c\nBnbfm4vk1/bSAO6TF/xEVubU0i4f6q8sIecfqvskEVMS7lkjeptPMR0DIakCQE+7\nuQH/Wizf+r0GXshplyOu4LVHisk63N7aMlAJ7XbuUHmWLKRmiReSfR8CBNzig/2X\nFmkMsUyw9hwte5zsrQcCQQCrOkZvzUj9j1HKG+32EJ2E4kisJZmAgF9GI+z6oxpi\nExped5tp8EWytCjRwKhOcc0068SgaqhKvyyUWpbx32VQ\n-----END RSA PRIVATE KEY-----` | Hard-coded private key. | main.go:13:1:27:30 | `-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQC/tzdtXKXcX6F3v3hR6+uYyZpIeXhhLflJkY2eILLQfAnwKlT5\nxIHW5QZcHQV9sCyZ8qSdPGif7PwgMbButMbByiZhCSugUFb6vjVqoktmslYF4LKH\niDgvmlwuJW0TvynxBLzDCwrRP+gpRT8wuAortWAx/03POTw7Mzi2cIPNsQIDAQAB\nAoGAMHCrqY9CPTdQhgAz94cDpTwzJmLCvtMt7J/BR5X9eF4O6MbZZ652HAUMIVQX\n4hUUf+VmIHB2AwqO/ddwO9ijaz04BslOSy/iYevHGlH65q4587NSlFWjvILMIQCM\nGBjfzJIxlLHVhjc2cFnyAE5YWjF/OMnJN0OhP9pxmCP/iM0CQQDxmQndQLdnV7+6\n8SvBHE8bg1LE8/BzTt68U3aWwiBjrHMFgzr//7Za4VF7h4ilFgmbh0F3sYz+C8iO\n0JrBRPeLAkEAyyTwnv/pgqTS/wuxIHUxRBpbdk3YvILAthNrGQg5uzA7eSeFu7Mv\nGtEkXsaqCDbdehgarFfNN8PB6OMRIbsXMwJBAOjhH8UJ0L/osYO9XPO0GfznRS1c\nBnbfm4vk1/bSAO6TF/xEVubU0i4f6q8sIecfqvskEVMS7lkjeptPMR0DIakCQE+7\nuQH/Wizf+r0GXshplyOu4LVHisk63N7aMlAJ7XbuUHmWLKRmiReSfR8CBNzig/2X\nFmkMsUyw9hwte5zsrQcCQQCrOkZvzUj9j1HKG+32EJ2E4kisJZmAgF9GI+z6oxpi\nExped5tp8EWytCjRwKhOcc0068SgaqhKvyyUWpbx32VQ\n-----END RSA PRIVATE KEY-----` | | +| main.go:45:14:45:19 | "pass" | Hard-coded $@. | main.go:45:14:45:19 | "pass" | password | +| main.go:49:13:49:15 | tmp | Hard-coded $@. | main.go:45:14:45:19 | "pass" | password | +| main.go:51:15:51:21 | "pass2" | Hard-coded $@. | main.go:51:15:51:21 | "pass2" | password | diff --git a/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.go b/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.go new file mode 100644 index 00000000..3d97a316 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.go @@ -0,0 +1,20 @@ +package main + +import ( + "database/sql" + "fmt" +) + +const ( + user = "dbuser" + password = "secretpassword" +) + +func connect() *sql.DB { + connStr := fmt.Sprintf("postgres://%s:%s@localhost/pqgotest", user, password) + db, err := sql.Open("postgres", connStr) + if err != nil { + return nil + } + return db +} diff --git a/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.qlref b/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.qlref new file mode 100644 index 00000000..2b0cf298 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-798/HardcodedCredentials.qlref @@ -0,0 +1 @@ +Security/CWE-798/HardcodedCredentials.ql diff --git a/ql/test/query-tests/Security/CWE-798/main.go b/ql/test/query-tests/Security/CWE-798/main.go new file mode 100644 index 00000000..341b23ff --- /dev/null +++ b/ql/test/query-tests/Security/CWE-798/main.go @@ -0,0 +1,58 @@ +package main + +import "fmt" + +const ( + passwd = "passw0rd" // NOT OK + notAPassword = "hello" // OK + _password = "" // OK +) + +// generated using http://travistidwell.com/jsencrypt/demo +const totallyNotAPrivateKey = // NOT OK +`-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC/tzdtXKXcX6F3v3hR6+uYyZpIeXhhLflJkY2eILLQfAnwKlT5 +xIHW5QZcHQV9sCyZ8qSdPGif7PwgMbButMbByiZhCSugUFb6vjVqoktmslYF4LKH +iDgvmlwuJW0TvynxBLzDCwrRP+gpRT8wuAortWAx/03POTw7Mzi2cIPNsQIDAQAB +AoGAMHCrqY9CPTdQhgAz94cDpTwzJmLCvtMt7J/BR5X9eF4O6MbZZ652HAUMIVQX +4hUUf+VmIHB2AwqO/ddwO9ijaz04BslOSy/iYevHGlH65q4587NSlFWjvILMIQCM +GBjfzJIxlLHVhjc2cFnyAE5YWjF/OMnJN0OhP9pxmCP/iM0CQQDxmQndQLdnV7+6 +8SvBHE8bg1LE8/BzTt68U3aWwiBjrHMFgzr//7Za4VF7h4ilFgmbh0F3sYz+C8iO +0JrBRPeLAkEAyyTwnv/pgqTS/wuxIHUxRBpbdk3YvILAthNrGQg5uzA7eSeFu7Mv +GtEkXsaqCDbdehgarFfNN8PB6OMRIbsXMwJBAOjhH8UJ0L/osYO9XPO0GfznRS1c +Bnbfm4vk1/bSAO6TF/xEVubU0i4f6q8sIecfqvskEVMS7lkjeptPMR0DIakCQE+7 +uQH/Wizf+r0GXshplyOu4LVHisk63N7aMlAJ7XbuUHmWLKRmiReSfR8CBNzig/2X +FmkMsUyw9hwte5zsrQcCQQCrOkZvzUj9j1HKG+32EJ2E4kisJZmAgF9GI+z6oxpi +Exped5tp8EWytCjRwKhOcc0068SgaqhKvyyUWpbx32VQ +-----END RSA PRIVATE KEY-----` + +const publicKey = // OK +`-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/tzdtXKXcX6F3v3hR6+uYyZpI +eXhhLflJkY2eILLQfAnwKlT5xIHW5QZcHQV9sCyZ8qSdPGif7PwgMbButMbByiZh +CSugUFb6vjVqoktmslYF4LKHiDgvmlwuJW0TvynxBLzDCwrRP+gpRT8wuAortWAx +/03POTw7Mzi2cIPNsQIDAQAB +-----END PUBLIC KEY-----` + +var secretKey = "" // OK + +type info struct { + username string + password string +} + +func main() { + password := "pass" // NOT OK + tmp := password + i := info{ + username: "me", + password: tmp, // NOT OK + } + i.password = "pass2" // NOT OK + fmt.Println(password, i) + testPassword := "pass" // OK + i.password = "test" // OK + i.password = testPassword // OK + secretKey = "secret" // OK + i.password = "--- redacted ---" // OK +} diff --git a/ql/test/query-tests/definitions/definitions.expected b/ql/test/query-tests/definitions/definitions.expected new file mode 100644 index 00000000..3c10cc9a --- /dev/null +++ b/ql/test/query-tests/definitions/definitions.expected @@ -0,0 +1,4 @@ +| main.go:6:26:6:28 | who | main.go:5:12:5:14 | who | V | +| main.go:11:2:11:6 | greet | main.go:5:6:5:10 | greet | V | +| main.go:11:8:11:12 | world | main.go:10:2:10:6 | world | V | +| main.go:12:2:12:7 | greet2 | greet.go:5:6:5:11 | greet2 | V | diff --git a/ql/test/query-tests/definitions/definitions.qlref b/ql/test/query-tests/definitions/definitions.qlref new file mode 100644 index 00000000..7b600c09 --- /dev/null +++ b/ql/test/query-tests/definitions/definitions.qlref @@ -0,0 +1 @@ +definitions.ql diff --git a/ql/test/query-tests/definitions/greet.go b/ql/test/query-tests/definitions/greet.go new file mode 100644 index 00000000..064e43a2 --- /dev/null +++ b/ql/test/query-tests/definitions/greet.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func greet2() { + fmt.Println("Hello world!") +} diff --git a/ql/test/query-tests/definitions/main.go b/ql/test/query-tests/definitions/main.go new file mode 100644 index 00000000..1483d6e2 --- /dev/null +++ b/ql/test/query-tests/definitions/main.go @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func greet(who string) { + fmt.Printf("Hello %s!", who) +} + +func main() { + world := "world" + greet(world) + greet2() +} diff --git a/ql/test/query-tests/filters/ClassifyFiles/ClassifyFiles.expected b/ql/test/query-tests/filters/ClassifyFiles/ClassifyFiles.expected new file mode 100644 index 00000000..2e2e4217 --- /dev/null +++ b/ql/test/query-tests/filters/ClassifyFiles/ClassifyFiles.expected @@ -0,0 +1 @@ +| hello.go:0:0:0:0 | hello.go | generated | diff --git a/ql/test/query-tests/filters/ClassifyFiles/ClassifyFiles.qlref b/ql/test/query-tests/filters/ClassifyFiles/ClassifyFiles.qlref new file mode 100644 index 00000000..cee3b1fe --- /dev/null +++ b/ql/test/query-tests/filters/ClassifyFiles/ClassifyFiles.qlref @@ -0,0 +1 @@ +filters/ClassifyFiles.ql diff --git a/ql/test/query-tests/filters/ClassifyFiles/hello.go b/ql/test/query-tests/filters/ClassifyFiles/hello.go new file mode 100644 index 00000000..061fd0b0 --- /dev/null +++ b/ql/test/query-tests/filters/ClassifyFiles/hello.go @@ -0,0 +1,9 @@ +// Code generated by hello-world-generator; DO NOT EDIT. + +package main + +import "fmt" + +func main() { + fmt.Println("Hello, world!") +} diff --git a/templates/project/project b/templates/project/project new file mode 100644 index 00000000..7dbe3289 --- /dev/null +++ b/templates/project/project @@ -0,0 +1,9 @@ + + 600 + + git clone -n ${repository} ${src} + git checkout ${revision} + ${semmle_dist}/language-packs/go/tools/platform/${semmle_platform}/bin/go-autobuilder + odasa duplicateCode --ram 2048 --minimum-tokens 100 + + diff --git a/templates/project/variables b/templates/project/variables new file mode 100644 index 00000000..71cf40dd --- /dev/null +++ b/templates/project/variables @@ -0,0 +1,4 @@ +LGTM_SRC=${src} +SEMMLE_REPO_URL=${repository} +LGTM_THREADS= +revision=HEAD diff --git a/tools/bootstrap.cmd b/tools/bootstrap.cmd new file mode 100644 index 00000000..663325f2 --- /dev/null +++ b/tools/bootstrap.cmd @@ -0,0 +1 @@ +%~dp0\platform\win\bin\go-bootstrap.exe %1 %2 diff --git a/tools/bootstrap.sh b/tools/bootstrap.sh new file mode 100755 index 00000000..943799b3 --- /dev/null +++ b/tools/bootstrap.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPTDIR/utils.sh" +run go-bootstrap "$@" diff --git a/tools/index.cmd b/tools/index.cmd new file mode 100644 index 00000000..4611e7c9 --- /dev/null +++ b/tools/index.cmd @@ -0,0 +1 @@ +%~dp0\platform\win\bin\go-autobuilder.exe diff --git a/tools/index.sh b/tools/index.sh new file mode 100755 index 00000000..ead609fd --- /dev/null +++ b/tools/index.sh @@ -0,0 +1,5 @@ +#! /bin/bash +set -ex +SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPTDIR/utils.sh" +run go-autobuilder diff --git a/tools/qltest.cmd b/tools/qltest.cmd new file mode 100644 index 00000000..34277717 --- /dev/null +++ b/tools/qltest.cmd @@ -0,0 +1 @@ +%~dp0\platform\win\bin\go-extractor.exe -mod=vendor ./... diff --git a/tools/qltest.sh b/tools/qltest.sh new file mode 100755 index 00000000..0e6ef95f --- /dev/null +++ b/tools/qltest.sh @@ -0,0 +1,5 @@ +#! /bin/bash +set -e +SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPTDIR/utils.sh" +run go-extractor -mod=vendor ./... diff --git a/tools/utils.sh b/tools/utils.sh new file mode 100644 index 00000000..b67906ac --- /dev/null +++ b/tools/utils.sh @@ -0,0 +1,24 @@ +#! /bin/bash + +if [ -z "$SEMMLE_PLATFORM" ] +then + case "$OSTYPE" in + linux*) SEMMLE_PLATFORM="linux";; + darwin*) SEMMLE_PLATFORM="osx";; + msys*) SEMMLE_PLATFORM="win";; + *) echo "This script only works on Linux, macOS and msys; OSTYPE: $OSTYPE" && exit 1 + esac +fi + +if [ "$SEMMLE_PLATFORM" = "win" ] +then + EXE=".exe" +else + EXE="" +fi + +run() { + cmd=$1 + shift + "$SCRIPTDIR/platform/$SEMMLE_PLATFORM/bin/$cmd$EXE" "$@" +} diff --git a/upgrades/initial/go.dbscheme b/upgrades/initial/go.dbscheme new file mode 100644 index 00000000..ffced433 --- /dev/null +++ b/upgrades/initial/go.dbscheme @@ -0,0 +1,402 @@ +/** Auto-generated dbscheme; do not edit. */ + + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +locations_default(unique int id: @location_default, int file: @file ref, int beginLine: int ref, int beginColumn: int ref, + int endLine: int ref, int endColumn: int ref); + +numlines(int element_id: @sourceline ref, int num_lines: int ref, int num_code: int ref, int num_comment: int ref); + +files(unique int id: @file, string name: string ref, string simple: string ref, string ext: string ref, int fromSource: int ref); + +folders(unique int id: @folder, string name: string ref, string simple: string ref); + +containerparent(int parent: @container ref, unique int child: @container ref); + +has_location(unique int locatable: @locatable ref, int location: @location ref); + +comment_groups(unique int id: @comment_group); + +comments(unique int id: @comment, int kind: int ref, int parent: @comment_group ref, int idx: int ref, string text: string ref); + +doc_comments(unique int node: @documentable ref, int comment: @comment_group ref); + +#keyset[parent, idx] +exprs(unique int id: @expr, int kind: int ref, int parent: @exprparent ref, int idx: int ref); + +literals(unique int expr: @expr ref, string value: string ref, string raw: string ref); + +constvalues(unique int expr: @expr ref, string value: string ref, string exact: string ref); + +fields(unique int id: @field, int parent: @fieldparent ref, int idx: int ref); + +#keyset[parent, idx] +stmts(unique int id: @stmt, int kind: int ref, int parent: @stmtparent ref, int idx: int ref); + +#keyset[parent, idx] +decls(unique int id: @decl, int kind: int ref, int parent: @declparent ref, int idx: int ref); + +#keyset[parent, idx] +specs(unique int id: @spec, int kind: int ref, int parent: @gendecl ref, int idx: int ref); + +scopes(unique int id: @scope, int kind: int ref); + +scopenesting(unique int inner: @scope ref, int outer: @scope ref); + +scopenodes(unique int node: @scopenode ref, int scope: @localscope ref); + +objects(unique int id: @object, int kind: int ref, string name: string ref); + +objectscopes(unique int object: @object ref, int scope: @scope ref); + +objecttypes(unique int object: @object ref, int tp: @type ref); + +methodreceivers(unique int method: @object ref, int receiver: @object ref); + +fieldstructs(unique int field: @object ref, int struct: @structtype ref); + +defs(int ident: @ident ref, int object: @object ref); + +uses(int ident: @ident ref, int object: @object ref); + +types(unique int id: @type, int kind: int ref); + +type_of(unique int expr: @expr ref, int tp: @type ref); + +typename(unique int tp: @type ref, string name: string ref); + +key_type(unique int map: @maptype ref, int tp: @type ref); + +element_type(unique int container: @containertype ref, int tp: @type ref); + +base_type(unique int ptr: @pointertype ref, int tp: @type ref); + +underlying_type(unique int named: @namedtype ref, int tp: @type ref); + +#keyset[parent, index] +component_types(int parent: @compositetype ref, int index: int ref, string name: string ref, int tp: @type ref); + +array_length(unique int tp: @arraytype ref, string len: string ref); + +type_objects(unique int tp: @type ref, int object: @object ref); + +packages(unique int id: @package, string name: string ref, string path: string ref, int scope: @packagescope ref); + +@container = @file | @folder; + +@locatable = @node | @localscope; + +@node = @documentable | @exprparent | @fieldparent | @stmtparent | @declparent | @scopenode | @comment_group | @comment; + +@documentable = @file | @field | @spec | @gendecl | @funcdecl; + +@exprparent = @funcdef | @file | @expr | @field | @stmt | @decl | @spec; + +@fieldparent = @decl | @structtypeexpr | @functypeexpr | @interfacetypeexpr; + +@stmtparent = @funcdef | @stmt | @decl; + +@declparent = @file | @declstmt; + +@funcdef = @funclit | @funcdecl; + +@scopenode = @file | @functypeexpr | @blockstmt | @ifstmt | @caseclause | @switchstmt | @commclause | @loopstmt; + +@location = @location_default; + +@sourceline = @locatable; + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment; + +case @expr.kind of + 0 = @badexpr +| 1 = @ident +| 2 = @ellipsis +| 3 = @intlit +| 4 = @floatlit +| 5 = @imaglit +| 6 = @charlit +| 7 = @stringlit +| 8 = @funclit +| 9 = @compositelit +| 10 = @parenexpr +| 11 = @selectorexpr +| 12 = @indexexpr +| 13 = @sliceexpr +| 14 = @typeassertexpr +| 15 = @callorconversionexpr +| 16 = @starexpr +| 17 = @keyvalueexpr +| 18 = @arraytypeexpr +| 19 = @structtypeexpr +| 20 = @functypeexpr +| 21 = @interfacetypeexpr +| 22 = @maptypeexpr +| 23 = @plusexpr +| 24 = @minusexpr +| 25 = @notexpr +| 26 = @complementexpr +| 27 = @derefexpr +| 28 = @addressexpr +| 29 = @arrowexpr +| 30 = @lorexpr +| 31 = @landexpr +| 32 = @eqlexpr +| 33 = @neqexpr +| 34 = @lssexpr +| 35 = @leqexpr +| 36 = @gtrexpr +| 37 = @geqexpr +| 38 = @addexpr +| 39 = @subexpr +| 40 = @orexpr +| 41 = @xorexpr +| 42 = @mulexpr +| 43 = @quoexpr +| 44 = @remexpr +| 45 = @shlexpr +| 46 = @shrexpr +| 47 = @andexpr +| 48 = @andnotexpr +| 49 = @sendchantypeexpr +| 50 = @recvchantypeexpr +| 51 = @sendrcvchantypeexpr; + +@basiclit = @intlit | @floatlit | @imaglit | @charlit | @stringlit; + +@operatorexpr = @logicalexpr | @arithmeticexpr | @bitwiseexpr | @unaryexpr | @binaryexpr; + +@logicalexpr = @logicalunaryexpr | @logicalbinaryexpr; + +@arithmeticexpr = @arithmeticunaryexpr | @arithmeticbinaryexpr; + +@bitwiseexpr = @bitwiseunaryexpr | @bitwisebinaryexpr; + +@unaryexpr = @logicalunaryexpr | @bitwiseunaryexpr | @arithmeticunaryexpr | @derefexpr | @addressexpr | @arrowexpr; + +@logicalunaryexpr = @notexpr; + +@bitwiseunaryexpr = @complementexpr; + +@arithmeticunaryexpr = @plusexpr | @minusexpr; + +@binaryexpr = @logicalbinaryexpr | @bitwisebinaryexpr | @arithmeticbinaryexpr | @comparison; + +@logicalbinaryexpr = @lorexpr | @landexpr; + +@bitwisebinaryexpr = @shiftexpr | @orexpr | @xorexpr | @andexpr | @andnotexpr; + +@arithmeticbinaryexpr = @addexpr | @subexpr | @mulexpr | @quoexpr | @remexpr; + +@shiftexpr = @shlexpr | @shrexpr; + +@comparison = @equalitytest | @relationalcomparison; + +@equalitytest = @eqlexpr | @neqexpr; + +@relationalcomparison = @lssexpr | @leqexpr | @gtrexpr | @geqexpr; + +@chantypeexpr = @sendchantypeexpr | @recvchantypeexpr | @sendrcvchantypeexpr; + +case @stmt.kind of + 0 = @badstmt +| 1 = @declstmt +| 2 = @emptystmt +| 3 = @labeledstmt +| 4 = @exprstmt +| 5 = @sendstmt +| 6 = @incstmt +| 7 = @decstmt +| 8 = @gostmt +| 9 = @deferstmt +| 10 = @returnstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @gotostmt +| 14 = @fallthroughstmt +| 15 = @blockstmt +| 16 = @ifstmt +| 17 = @caseclause +| 18 = @exprswitchstmt +| 19 = @typeswitchstmt +| 20 = @commclause +| 21 = @selectstmt +| 22 = @forstmt +| 23 = @rangestmt +| 24 = @assignstmt +| 25 = @definestmt +| 26 = @addassignstmt +| 27 = @subassignstmt +| 28 = @mulassignstmt +| 29 = @quoassignstmt +| 30 = @remassignstmt +| 31 = @andassignstmt +| 32 = @orassignstmt +| 33 = @xorassignstmt +| 34 = @shlassignstmt +| 35 = @shrassignstmt +| 36 = @andnotassignstmt; + +@incdecstmt = @incstmt | @decstmt; + +@assignment = @simpleassignstmt | @compoundassignstmt; + +@simpleassignstmt = @assignstmt | @definestmt; + +@compoundassignstmt = @addassignstmt | @subassignstmt | @mulassignstmt | @quoassignstmt | @remassignstmt + | @andassignstmt | @orassignstmt | @xorassignstmt | @shlassignstmt | @shrassignstmt | @andnotassignstmt; + +@branchstmt = @breakstmt | @continuestmt | @gotostmt | @fallthroughstmt; + +@switchstmt = @exprswitchstmt | @typeswitchstmt; + +@loopstmt = @forstmt | @rangestmt; + +case @decl.kind of + 0 = @baddecl +| 1 = @importdecl +| 2 = @constdecl +| 3 = @typedecl +| 4 = @vardecl +| 5 = @funcdecl; + +@gendecl = @importdecl | @constdecl | @typedecl | @vardecl; + +case @spec.kind of + 0 = @importspec +| 1 = @valuespec +| 2 = @typespec; + +case @object.kind of + 0 = @pkgobject +| 1 = @decltypeobject +| 2 = @builtintypeobject +| 3 = @declconstobject +| 4 = @builtinconstobject +| 5 = @declvarobject +| 6 = @declfunctionobject +| 7 = @builtinfunctionobject +| 8 = @labelobject; + +@declobject = @decltypeobject | @declconstobject | @declvarobject | @declfunctionobject; + +@builtinobject = @builtintypeobject | @builtinconstobject | @builtinfunctionobject; + +@typeobject = @decltypeobject | @builtintypeobject; + +@valueobject = @constobject | @varobject | @functionobject; + +@constobject = @declconstobject | @builtinconstobject; + +@varobject = @declvarobject; + +@functionobject = @declfunctionobject | @builtinfunctionobject; + +case @scope.kind of + 0 = @universescope +| 1 = @packagescope +| 2 = @localscope; + +case @type.kind of + 0 = @invalidtype +| 1 = @boolexprtype +| 2 = @inttype +| 3 = @int8type +| 4 = @int16type +| 5 = @int32type +| 6 = @int64type +| 7 = @uinttype +| 8 = @uint8type +| 9 = @uint16type +| 10 = @uint32type +| 11 = @uint64type +| 12 = @uintptrtype +| 13 = @float32type +| 14 = @float64type +| 15 = @complex64type +| 16 = @complex128type +| 17 = @stringexprtype +| 18 = @unsafepointertype +| 19 = @boolliteraltype +| 20 = @intliteraltype +| 21 = @runeliteraltype +| 22 = @floatliteraltype +| 23 = @complexliteraltype +| 24 = @stringliteraltype +| 25 = @nilliteraltype +| 26 = @arraytype +| 27 = @slicetype +| 28 = @structtype +| 29 = @pointertype +| 30 = @interfacetype +| 31 = @tupletype +| 32 = @signaturetype +| 33 = @maptype +| 34 = @sendchantype +| 35 = @recvchantype +| 36 = @sendrcvchantype +| 37 = @namedtype; + +@basictype = @booltype | @numerictype | @stringtype | @literaltype | @invalidtype | @uintptrtype | @unsafepointertype; + +@booltype = @boolexprtype | @boolliteraltype; + +@numerictype = @integertype | @floattype | @complextype; + +@integertype = @signedintegertype | @unsignedintegertype; + +@signedintegertype = @inttype | @int8type | @int16type | @int32type | @int64type | @intliteraltype | @runeliteraltype; + +@unsignedintegertype = @uinttype | @uint8type | @uint16type | @uint32type | @uint64type; + +@floattype = @float32type | @float64type | @floatliteraltype; + +@complextype = @complex64type | @complex128type | @complexliteraltype; + +@stringtype = @stringexprtype | @stringliteraltype; + +@literaltype = @boolliteraltype | @intliteraltype | @runeliteraltype | @floatliteraltype | @complexliteraltype + | @stringliteraltype | @nilliteraltype; + +@compositetype = @containertype | @structtype | @pointertype | @interfacetype | @tupletype | @signaturetype | @namedtype; + +@containertype = @arraytype | @slicetype | @maptype | @chantype; + +@chantype = @sendchantype | @recvchantype | @sendrcvchantype; + diff --git a/vendor/golang.org/x/tools/AUTHORS b/vendor/golang.org/x/tools/AUTHORS new file mode 100644 index 00000000..15167cd7 --- /dev/null +++ b/vendor/golang.org/x/tools/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/tools/CONTRIBUTORS b/vendor/golang.org/x/tools/CONTRIBUTORS new file mode 100644 index 00000000..1c4577e9 --- /dev/null +++ b/vendor/golang.org/x/tools/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/tools/LICENSE b/vendor/golang.org/x/tools/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/vendor/golang.org/x/tools/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/tools/PATENTS b/vendor/golang.org/x/tools/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/vendor/golang.org/x/tools/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go new file mode 100644 index 00000000..f8363d8f --- /dev/null +++ b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go @@ -0,0 +1,109 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gcexportdata provides functions for locating, reading, and +// writing export data files containing type information produced by the +// gc compiler. This package supports go1.7 export data format and all +// later versions. +// +// Although it might seem convenient for this package to live alongside +// go/types in the standard library, this would cause version skew +// problems for developer tools that use it, since they must be able to +// consume the outputs of the gc compiler both before and after a Go +// update such as from Go 1.7 to Go 1.8. Because this package lives in +// golang.org/x/tools, sites can update their version of this repo some +// time before the Go 1.8 release and rebuild and redeploy their +// developer tools, which will then be able to consume both Go 1.7 and +// Go 1.8 export data files, so they will work before and after the +// Go update. (See discussion at https://golang.org/issue/15651.) +// +package gcexportdata // import "golang.org/x/tools/go/gcexportdata" + +import ( + "bufio" + "bytes" + "fmt" + "go/token" + "go/types" + "io" + "io/ioutil" + + "golang.org/x/tools/go/internal/gcimporter" +) + +// Find returns the name of an object (.o) or archive (.a) file +// containing type information for the specified import path, +// using the workspace layout conventions of go/build. +// If no file was found, an empty filename is returned. +// +// A relative srcDir is interpreted relative to the current working directory. +// +// Find also returns the package's resolved (canonical) import path, +// reflecting the effects of srcDir and vendoring on importPath. +func Find(importPath, srcDir string) (filename, path string) { + return gcimporter.FindPkg(importPath, srcDir) +} + +// NewReader returns a reader for the export data section of an object +// (.o) or archive (.a) file read from r. The new reader may provide +// additional trailing data beyond the end of the export data. +func NewReader(r io.Reader) (io.Reader, error) { + buf := bufio.NewReader(r) + _, err := gcimporter.FindExportData(buf) + // If we ever switch to a zip-like archive format with the ToC + // at the end, we can return the correct portion of export data, + // but for now we must return the entire rest of the file. + return buf, err +} + +// Read reads export data from in, decodes it, and returns type +// information for the package. +// The package name is specified by path. +// File position information is added to fset. +// +// Read may inspect and add to the imports map to ensure that references +// within the export data to other packages are consistent. The caller +// must ensure that imports[path] does not exist, or exists but is +// incomplete (see types.Package.Complete), and Read inserts the +// resulting package into this map entry. +// +// On return, the state of the reader is undefined. +func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) { + data, err := ioutil.ReadAll(in) + if err != nil { + return nil, fmt.Errorf("reading export data for %q: %v", path, err) + } + + if bytes.HasPrefix(data, []byte("!")) { + return nil, fmt.Errorf("can't read export data for %q directly from an archive file (call gcexportdata.NewReader first to extract export data)", path) + } + + // The App Engine Go runtime v1.6 uses the old export data format. + // TODO(adonovan): delete once v1.7 has been around for a while. + if bytes.HasPrefix(data, []byte("package ")) { + return gcimporter.ImportData(imports, path, path, bytes.NewReader(data)) + } + + // The indexed export format starts with an 'i'; the older + // binary export format starts with a 'c', 'd', or 'v' + // (from "version"). Select appropriate importer. + if len(data) > 0 && data[0] == 'i' { + _, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path) + return pkg, err + } + + _, pkg, err := gcimporter.BImportData(fset, imports, data, path) + return pkg, err +} + +// Write writes encoded type information for the specified package to out. +// The FileSet provides file position information for named objects. +func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error { + b, err := gcimporter.IExportData(fset, pkg) + if err != nil { + return err + } + _, err = out.Write(b) + return err +} diff --git a/vendor/golang.org/x/tools/go/gcexportdata/importer.go b/vendor/golang.org/x/tools/go/gcexportdata/importer.go new file mode 100644 index 00000000..efe221e7 --- /dev/null +++ b/vendor/golang.org/x/tools/go/gcexportdata/importer.go @@ -0,0 +1,73 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcexportdata + +import ( + "fmt" + "go/token" + "go/types" + "os" +) + +// NewImporter returns a new instance of the types.Importer interface +// that reads type information from export data files written by gc. +// The Importer also satisfies types.ImporterFrom. +// +// Export data files are located using "go build" workspace conventions +// and the build.Default context. +// +// Use this importer instead of go/importer.For("gc", ...) to avoid the +// version-skew problems described in the documentation of this package, +// or to control the FileSet or access the imports map populated during +// package loading. +// +func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom { + return importer{fset, imports} +} + +type importer struct { + fset *token.FileSet + imports map[string]*types.Package +} + +func (imp importer) Import(importPath string) (*types.Package, error) { + return imp.ImportFrom(importPath, "", 0) +} + +func (imp importer) ImportFrom(importPath, srcDir string, mode types.ImportMode) (_ *types.Package, err error) { + filename, path := Find(importPath, srcDir) + if filename == "" { + if importPath == "unsafe" { + // Even for unsafe, call Find first in case + // the package was vendored. + return types.Unsafe, nil + } + return nil, fmt.Errorf("can't find import: %s", importPath) + } + + if pkg, ok := imp.imports[path]; ok && pkg.Complete() { + return pkg, nil // cache hit + } + + // open file + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + f.Close() + if err != nil { + // add file name to error + err = fmt.Errorf("reading export data: %s: %v", filename, err) + } + }() + + r, err := NewReader(f) + if err != nil { + return nil, err + } + + return Read(r, imp.fset, imp.imports, path) +} diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/bexport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/bexport.go new file mode 100644 index 00000000..a807d0aa --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/bexport.go @@ -0,0 +1,852 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Binary package export. +// This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go; +// see that file for specification of the format. + +package gcimporter + +import ( + "bytes" + "encoding/binary" + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "math" + "math/big" + "sort" + "strings" +) + +// If debugFormat is set, each integer and string value is preceded by a marker +// and position information in the encoding. This mechanism permits an importer +// to recognize immediately when it is out of sync. The importer recognizes this +// mode automatically (i.e., it can import export data produced with debugging +// support even if debugFormat is not set at the time of import). This mode will +// lead to massively larger export data (by a factor of 2 to 3) and should only +// be enabled during development and debugging. +// +// NOTE: This flag is the first flag to enable if importing dies because of +// (suspected) format errors, and whenever a change is made to the format. +const debugFormat = false // default: false + +// If trace is set, debugging output is printed to std out. +const trace = false // default: false + +// Current export format version. Increase with each format change. +// Note: The latest binary (non-indexed) export format is at version 6. +// This exporter is still at level 4, but it doesn't matter since +// the binary importer can handle older versions just fine. +// 6: package height (CL 105038) -- NOT IMPLEMENTED HERE +// 5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMEMTED HERE +// 4: type name objects support type aliases, uses aliasTag +// 3: Go1.8 encoding (same as version 2, aliasTag defined but never used) +// 2: removed unused bool in ODCL export (compiler only) +// 1: header format change (more regular), export package for _ struct fields +// 0: Go1.7 encoding +const exportVersion = 4 + +// trackAllTypes enables cycle tracking for all types, not just named +// types. The existing compiler invariants assume that unnamed types +// that are not completely set up are not used, or else there are spurious +// errors. +// If disabled, only named types are tracked, possibly leading to slightly +// less efficient encoding in rare cases. It also prevents the export of +// some corner-case type declarations (but those are not handled correctly +// with with the textual export format either). +// TODO(gri) enable and remove once issues caused by it are fixed +const trackAllTypes = false + +type exporter struct { + fset *token.FileSet + out bytes.Buffer + + // object -> index maps, indexed in order of serialization + strIndex map[string]int + pkgIndex map[*types.Package]int + typIndex map[types.Type]int + + // position encoding + posInfoFormat bool + prevFile string + prevLine int + + // debugging support + written int // bytes written + indent int // for trace +} + +// internalError represents an error generated inside this package. +type internalError string + +func (e internalError) Error() string { return "gcimporter: " + string(e) } + +func internalErrorf(format string, args ...interface{}) error { + return internalError(fmt.Sprintf(format, args...)) +} + +// BExportData returns binary export data for pkg. +// If no file set is provided, position info will be missing. +func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) { + defer func() { + if e := recover(); e != nil { + if ierr, ok := e.(internalError); ok { + err = ierr + return + } + // Not an internal error; panic again. + panic(e) + } + }() + + p := exporter{ + fset: fset, + strIndex: map[string]int{"": 0}, // empty string is mapped to 0 + pkgIndex: make(map[*types.Package]int), + typIndex: make(map[types.Type]int), + posInfoFormat: true, // TODO(gri) might become a flag, eventually + } + + // write version info + // The version string must start with "version %d" where %d is the version + // number. Additional debugging information may follow after a blank; that + // text is ignored by the importer. + p.rawStringln(fmt.Sprintf("version %d", exportVersion)) + var debug string + if debugFormat { + debug = "debug" + } + p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly + p.bool(trackAllTypes) + p.bool(p.posInfoFormat) + + // --- generic export data --- + + // populate type map with predeclared "known" types + for index, typ := range predeclared() { + p.typIndex[typ] = index + } + if len(p.typIndex) != len(predeclared()) { + return nil, internalError("duplicate entries in type map?") + } + + // write package data + p.pkg(pkg, true) + if trace { + p.tracef("\n") + } + + // write objects + objcount := 0 + scope := pkg.Scope() + for _, name := range scope.Names() { + if !ast.IsExported(name) { + continue + } + if trace { + p.tracef("\n") + } + p.obj(scope.Lookup(name)) + objcount++ + } + + // indicate end of list + if trace { + p.tracef("\n") + } + p.tag(endTag) + + // for self-verification only (redundant) + p.int(objcount) + + if trace { + p.tracef("\n") + } + + // --- end of export data --- + + return p.out.Bytes(), nil +} + +func (p *exporter) pkg(pkg *types.Package, emptypath bool) { + if pkg == nil { + panic(internalError("unexpected nil pkg")) + } + + // if we saw the package before, write its index (>= 0) + if i, ok := p.pkgIndex[pkg]; ok { + p.index('P', i) + return + } + + // otherwise, remember the package, write the package tag (< 0) and package data + if trace { + p.tracef("P%d = { ", len(p.pkgIndex)) + defer p.tracef("} ") + } + p.pkgIndex[pkg] = len(p.pkgIndex) + + p.tag(packageTag) + p.string(pkg.Name()) + if emptypath { + p.string("") + } else { + p.string(pkg.Path()) + } +} + +func (p *exporter) obj(obj types.Object) { + switch obj := obj.(type) { + case *types.Const: + p.tag(constTag) + p.pos(obj) + p.qualifiedName(obj) + p.typ(obj.Type()) + p.value(obj.Val()) + + case *types.TypeName: + if obj.IsAlias() { + p.tag(aliasTag) + p.pos(obj) + p.qualifiedName(obj) + } else { + p.tag(typeTag) + } + p.typ(obj.Type()) + + case *types.Var: + p.tag(varTag) + p.pos(obj) + p.qualifiedName(obj) + p.typ(obj.Type()) + + case *types.Func: + p.tag(funcTag) + p.pos(obj) + p.qualifiedName(obj) + sig := obj.Type().(*types.Signature) + p.paramList(sig.Params(), sig.Variadic()) + p.paramList(sig.Results(), false) + + default: + panic(internalErrorf("unexpected object %v (%T)", obj, obj)) + } +} + +func (p *exporter) pos(obj types.Object) { + if !p.posInfoFormat { + return + } + + file, line := p.fileLine(obj) + if file == p.prevFile { + // common case: write line delta + // delta == 0 means different file or no line change + delta := line - p.prevLine + p.int(delta) + if delta == 0 { + p.int(-1) // -1 means no file change + } + } else { + // different file + p.int(0) + // Encode filename as length of common prefix with previous + // filename, followed by (possibly empty) suffix. Filenames + // frequently share path prefixes, so this can save a lot + // of space and make export data size less dependent on file + // path length. The suffix is unlikely to be empty because + // file names tend to end in ".go". + n := commonPrefixLen(p.prevFile, file) + p.int(n) // n >= 0 + p.string(file[n:]) // write suffix only + p.prevFile = file + p.int(line) + } + p.prevLine = line +} + +func (p *exporter) fileLine(obj types.Object) (file string, line int) { + if p.fset != nil { + pos := p.fset.Position(obj.Pos()) + file = pos.Filename + line = pos.Line + } + return +} + +func commonPrefixLen(a, b string) int { + if len(a) > len(b) { + a, b = b, a + } + // len(a) <= len(b) + i := 0 + for i < len(a) && a[i] == b[i] { + i++ + } + return i +} + +func (p *exporter) qualifiedName(obj types.Object) { + p.string(obj.Name()) + p.pkg(obj.Pkg(), false) +} + +func (p *exporter) typ(t types.Type) { + if t == nil { + panic(internalError("nil type")) + } + + // Possible optimization: Anonymous pointer types *T where + // T is a named type are common. We could canonicalize all + // such types *T to a single type PT = *T. This would lead + // to at most one *T entry in typIndex, and all future *T's + // would be encoded as the respective index directly. Would + // save 1 byte (pointerTag) per *T and reduce the typIndex + // size (at the cost of a canonicalization map). We can do + // this later, without encoding format change. + + // if we saw the type before, write its index (>= 0) + if i, ok := p.typIndex[t]; ok { + p.index('T', i) + return + } + + // otherwise, remember the type, write the type tag (< 0) and type data + if trackAllTypes { + if trace { + p.tracef("T%d = {>\n", len(p.typIndex)) + defer p.tracef("<\n} ") + } + p.typIndex[t] = len(p.typIndex) + } + + switch t := t.(type) { + case *types.Named: + if !trackAllTypes { + // if we don't track all types, track named types now + p.typIndex[t] = len(p.typIndex) + } + + p.tag(namedTag) + p.pos(t.Obj()) + p.qualifiedName(t.Obj()) + p.typ(t.Underlying()) + if !types.IsInterface(t) { + p.assocMethods(t) + } + + case *types.Array: + p.tag(arrayTag) + p.int64(t.Len()) + p.typ(t.Elem()) + + case *types.Slice: + p.tag(sliceTag) + p.typ(t.Elem()) + + case *dddSlice: + p.tag(dddTag) + p.typ(t.elem) + + case *types.Struct: + p.tag(structTag) + p.fieldList(t) + + case *types.Pointer: + p.tag(pointerTag) + p.typ(t.Elem()) + + case *types.Signature: + p.tag(signatureTag) + p.paramList(t.Params(), t.Variadic()) + p.paramList(t.Results(), false) + + case *types.Interface: + p.tag(interfaceTag) + p.iface(t) + + case *types.Map: + p.tag(mapTag) + p.typ(t.Key()) + p.typ(t.Elem()) + + case *types.Chan: + p.tag(chanTag) + p.int(int(3 - t.Dir())) // hack + p.typ(t.Elem()) + + default: + panic(internalErrorf("unexpected type %T: %s", t, t)) + } +} + +func (p *exporter) assocMethods(named *types.Named) { + // Sort methods (for determinism). + var methods []*types.Func + for i := 0; i < named.NumMethods(); i++ { + methods = append(methods, named.Method(i)) + } + sort.Sort(methodsByName(methods)) + + p.int(len(methods)) + + if trace && methods != nil { + p.tracef("associated methods {>\n") + } + + for i, m := range methods { + if trace && i > 0 { + p.tracef("\n") + } + + p.pos(m) + name := m.Name() + p.string(name) + if !exported(name) { + p.pkg(m.Pkg(), false) + } + + sig := m.Type().(*types.Signature) + p.paramList(types.NewTuple(sig.Recv()), false) + p.paramList(sig.Params(), sig.Variadic()) + p.paramList(sig.Results(), false) + p.int(0) // dummy value for go:nointerface pragma - ignored by importer + } + + if trace && methods != nil { + p.tracef("<\n} ") + } +} + +type methodsByName []*types.Func + +func (x methodsByName) Len() int { return len(x) } +func (x methodsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() } + +func (p *exporter) fieldList(t *types.Struct) { + if trace && t.NumFields() > 0 { + p.tracef("fields {>\n") + defer p.tracef("<\n} ") + } + + p.int(t.NumFields()) + for i := 0; i < t.NumFields(); i++ { + if trace && i > 0 { + p.tracef("\n") + } + p.field(t.Field(i)) + p.string(t.Tag(i)) + } +} + +func (p *exporter) field(f *types.Var) { + if !f.IsField() { + panic(internalError("field expected")) + } + + p.pos(f) + p.fieldName(f) + p.typ(f.Type()) +} + +func (p *exporter) iface(t *types.Interface) { + // TODO(gri): enable importer to load embedded interfaces, + // then emit Embeddeds and ExplicitMethods separately here. + p.int(0) + + n := t.NumMethods() + if trace && n > 0 { + p.tracef("methods {>\n") + defer p.tracef("<\n} ") + } + p.int(n) + for i := 0; i < n; i++ { + if trace && i > 0 { + p.tracef("\n") + } + p.method(t.Method(i)) + } +} + +func (p *exporter) method(m *types.Func) { + sig := m.Type().(*types.Signature) + if sig.Recv() == nil { + panic(internalError("method expected")) + } + + p.pos(m) + p.string(m.Name()) + if m.Name() != "_" && !ast.IsExported(m.Name()) { + p.pkg(m.Pkg(), false) + } + + // interface method; no need to encode receiver. + p.paramList(sig.Params(), sig.Variadic()) + p.paramList(sig.Results(), false) +} + +func (p *exporter) fieldName(f *types.Var) { + name := f.Name() + + if f.Anonymous() { + // anonymous field - we distinguish between 3 cases: + // 1) field name matches base type name and is exported + // 2) field name matches base type name and is not exported + // 3) field name doesn't match base type name (alias name) + bname := basetypeName(f.Type()) + if name == bname { + if ast.IsExported(name) { + name = "" // 1) we don't need to know the field name or package + } else { + name = "?" // 2) use unexported name "?" to force package export + } + } else { + // 3) indicate alias and export name as is + // (this requires an extra "@" but this is a rare case) + p.string("@") + } + } + + p.string(name) + if name != "" && !ast.IsExported(name) { + p.pkg(f.Pkg(), false) + } +} + +func basetypeName(typ types.Type) string { + switch typ := deref(typ).(type) { + case *types.Basic: + return typ.Name() + case *types.Named: + return typ.Obj().Name() + default: + return "" // unnamed type + } +} + +func (p *exporter) paramList(params *types.Tuple, variadic bool) { + // use negative length to indicate unnamed parameters + // (look at the first parameter only since either all + // names are present or all are absent) + n := params.Len() + if n > 0 && params.At(0).Name() == "" { + n = -n + } + p.int(n) + for i := 0; i < params.Len(); i++ { + q := params.At(i) + t := q.Type() + if variadic && i == params.Len()-1 { + t = &dddSlice{t.(*types.Slice).Elem()} + } + p.typ(t) + if n > 0 { + name := q.Name() + p.string(name) + if name != "_" { + p.pkg(q.Pkg(), false) + } + } + p.string("") // no compiler-specific info + } +} + +func (p *exporter) value(x constant.Value) { + if trace { + p.tracef("= ") + } + + switch x.Kind() { + case constant.Bool: + tag := falseTag + if constant.BoolVal(x) { + tag = trueTag + } + p.tag(tag) + + case constant.Int: + if v, exact := constant.Int64Val(x); exact { + // common case: x fits into an int64 - use compact encoding + p.tag(int64Tag) + p.int64(v) + return + } + // uncommon case: large x - use float encoding + // (powers of 2 will be encoded efficiently with exponent) + p.tag(floatTag) + p.float(constant.ToFloat(x)) + + case constant.Float: + p.tag(floatTag) + p.float(x) + + case constant.Complex: + p.tag(complexTag) + p.float(constant.Real(x)) + p.float(constant.Imag(x)) + + case constant.String: + p.tag(stringTag) + p.string(constant.StringVal(x)) + + case constant.Unknown: + // package contains type errors + p.tag(unknownTag) + + default: + panic(internalErrorf("unexpected value %v (%T)", x, x)) + } +} + +func (p *exporter) float(x constant.Value) { + if x.Kind() != constant.Float { + panic(internalErrorf("unexpected constant %v, want float", x)) + } + // extract sign (there is no -0) + sign := constant.Sign(x) + if sign == 0 { + // x == 0 + p.int(0) + return + } + // x != 0 + + var f big.Float + if v, exact := constant.Float64Val(x); exact { + // float64 + f.SetFloat64(v) + } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { + // TODO(gri): add big.Rat accessor to constant.Value. + r := valueToRat(num) + f.SetRat(r.Quo(r, valueToRat(denom))) + } else { + // Value too large to represent as a fraction => inaccessible. + // TODO(gri): add big.Float accessor to constant.Value. + f.SetFloat64(math.MaxFloat64) // FIXME + } + + // extract exponent such that 0.5 <= m < 1.0 + var m big.Float + exp := f.MantExp(&m) + + // extract mantissa as *big.Int + // - set exponent large enough so mant satisfies mant.IsInt() + // - get *big.Int from mant + m.SetMantExp(&m, int(m.MinPrec())) + mant, acc := m.Int(nil) + if acc != big.Exact { + panic(internalError("internal error")) + } + + p.int(sign) + p.int(exp) + p.string(string(mant.Bytes())) +} + +func valueToRat(x constant.Value) *big.Rat { + // Convert little-endian to big-endian. + // I can't believe this is necessary. + bytes := constant.Bytes(x) + for i := 0; i < len(bytes)/2; i++ { + bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] + } + return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) +} + +func (p *exporter) bool(b bool) bool { + if trace { + p.tracef("[") + defer p.tracef("= %v] ", b) + } + + x := 0 + if b { + x = 1 + } + p.int(x) + return b +} + +// ---------------------------------------------------------------------------- +// Low-level encoders + +func (p *exporter) index(marker byte, index int) { + if index < 0 { + panic(internalError("invalid index < 0")) + } + if debugFormat { + p.marker('t') + } + if trace { + p.tracef("%c%d ", marker, index) + } + p.rawInt64(int64(index)) +} + +func (p *exporter) tag(tag int) { + if tag >= 0 { + panic(internalError("invalid tag >= 0")) + } + if debugFormat { + p.marker('t') + } + if trace { + p.tracef("%s ", tagString[-tag]) + } + p.rawInt64(int64(tag)) +} + +func (p *exporter) int(x int) { + p.int64(int64(x)) +} + +func (p *exporter) int64(x int64) { + if debugFormat { + p.marker('i') + } + if trace { + p.tracef("%d ", x) + } + p.rawInt64(x) +} + +func (p *exporter) string(s string) { + if debugFormat { + p.marker('s') + } + if trace { + p.tracef("%q ", s) + } + // if we saw the string before, write its index (>= 0) + // (the empty string is mapped to 0) + if i, ok := p.strIndex[s]; ok { + p.rawInt64(int64(i)) + return + } + // otherwise, remember string and write its negative length and bytes + p.strIndex[s] = len(p.strIndex) + p.rawInt64(-int64(len(s))) + for i := 0; i < len(s); i++ { + p.rawByte(s[i]) + } +} + +// marker emits a marker byte and position information which makes +// it easy for a reader to detect if it is "out of sync". Used for +// debugFormat format only. +func (p *exporter) marker(m byte) { + p.rawByte(m) + // Enable this for help tracking down the location + // of an incorrect marker when running in debugFormat. + if false && trace { + p.tracef("#%d ", p.written) + } + p.rawInt64(int64(p.written)) +} + +// rawInt64 should only be used by low-level encoders. +func (p *exporter) rawInt64(x int64) { + var tmp [binary.MaxVarintLen64]byte + n := binary.PutVarint(tmp[:], x) + for i := 0; i < n; i++ { + p.rawByte(tmp[i]) + } +} + +// rawStringln should only be used to emit the initial version string. +func (p *exporter) rawStringln(s string) { + for i := 0; i < len(s); i++ { + p.rawByte(s[i]) + } + p.rawByte('\n') +} + +// rawByte is the bottleneck interface to write to p.out. +// rawByte escapes b as follows (any encoding does that +// hides '$'): +// +// '$' => '|' 'S' +// '|' => '|' '|' +// +// Necessary so other tools can find the end of the +// export data by searching for "$$". +// rawByte should only be used by low-level encoders. +func (p *exporter) rawByte(b byte) { + switch b { + case '$': + // write '$' as '|' 'S' + b = 'S' + fallthrough + case '|': + // write '|' as '|' '|' + p.out.WriteByte('|') + p.written++ + } + p.out.WriteByte(b) + p.written++ +} + +// tracef is like fmt.Printf but it rewrites the format string +// to take care of indentation. +func (p *exporter) tracef(format string, args ...interface{}) { + if strings.ContainsAny(format, "<>\n") { + var buf bytes.Buffer + for i := 0; i < len(format); i++ { + // no need to deal with runes + ch := format[i] + switch ch { + case '>': + p.indent++ + continue + case '<': + p.indent-- + continue + } + buf.WriteByte(ch) + if ch == '\n' { + for j := p.indent; j > 0; j-- { + buf.WriteString(". ") + } + } + } + format = buf.String() + } + fmt.Printf(format, args...) +} + +// Debugging support. +// (tagString is only used when tracing is enabled) +var tagString = [...]string{ + // Packages + -packageTag: "package", + + // Types + -namedTag: "named type", + -arrayTag: "array", + -sliceTag: "slice", + -dddTag: "ddd", + -structTag: "struct", + -pointerTag: "pointer", + -signatureTag: "signature", + -interfaceTag: "interface", + -mapTag: "map", + -chanTag: "chan", + + // Values + -falseTag: "false", + -trueTag: "true", + -int64Tag: "int64", + -floatTag: "float", + -fractionTag: "fraction", + -complexTag: "complex", + -stringTag: "string", + -unknownTag: "unknown", + + // Type aliases + -aliasTag: "alias", +} diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go new file mode 100644 index 00000000..e9f73d14 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go @@ -0,0 +1,1039 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is a copy of $GOROOT/src/go/internal/gcimporter/bimport.go. + +package gcimporter + +import ( + "encoding/binary" + "fmt" + "go/constant" + "go/token" + "go/types" + "sort" + "strconv" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +type importer struct { + imports map[string]*types.Package + data []byte + importpath string + buf []byte // for reading strings + version int // export format version + + // object lists + strList []string // in order of appearance + pathList []string // in order of appearance + pkgList []*types.Package // in order of appearance + typList []types.Type // in order of appearance + interfaceList []*types.Interface // for delayed completion only + trackAllTypes bool + + // position encoding + posInfoFormat bool + prevFile string + prevLine int + fake fakeFileSet + + // debugging support + debugFormat bool + read int // bytes read +} + +// BImportData imports a package from the serialized package data +// and returns the number of bytes consumed and a reference to the package. +// If the export data version is not recognized or the format is otherwise +// compromised, an error is returned. +func BImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { + // catch panics and return them as errors + const currentVersion = 6 + version := -1 // unknown version + defer func() { + if e := recover(); e != nil { + // Return a (possibly nil or incomplete) package unchanged (see #16088). + if version > currentVersion { + err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) + } else { + err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e) + } + } + }() + + p := importer{ + imports: imports, + data: data, + importpath: path, + version: version, + strList: []string{""}, // empty string is mapped to 0 + pathList: []string{""}, // empty string is mapped to 0 + fake: fakeFileSet{ + fset: fset, + files: make(map[string]*token.File), + }, + } + + // read version info + var versionstr string + if b := p.rawByte(); b == 'c' || b == 'd' { + // Go1.7 encoding; first byte encodes low-level + // encoding format (compact vs debug). + // For backward-compatibility only (avoid problems with + // old installed packages). Newly compiled packages use + // the extensible format string. + // TODO(gri) Remove this support eventually; after Go1.8. + if b == 'd' { + p.debugFormat = true + } + p.trackAllTypes = p.rawByte() == 'a' + p.posInfoFormat = p.int() != 0 + versionstr = p.string() + if versionstr == "v1" { + version = 0 + } + } else { + // Go1.8 extensible encoding + // read version string and extract version number (ignore anything after the version number) + versionstr = p.rawStringln(b) + if s := strings.SplitN(versionstr, " ", 3); len(s) >= 2 && s[0] == "version" { + if v, err := strconv.Atoi(s[1]); err == nil && v > 0 { + version = v + } + } + } + p.version = version + + // read version specific flags - extend as necessary + switch p.version { + // case currentVersion: + // ... + // fallthrough + case currentVersion, 5, 4, 3, 2, 1: + p.debugFormat = p.rawStringln(p.rawByte()) == "debug" + p.trackAllTypes = p.int() != 0 + p.posInfoFormat = p.int() != 0 + case 0: + // Go1.7 encoding format - nothing to do here + default: + errorf("unknown bexport format version %d (%q)", p.version, versionstr) + } + + // --- generic export data --- + + // populate typList with predeclared "known" types + p.typList = append(p.typList, predeclared()...) + + // read package data + pkg = p.pkg() + + // read objects of phase 1 only (see cmd/compile/internal/gc/bexport.go) + objcount := 0 + for { + tag := p.tagOrIndex() + if tag == endTag { + break + } + p.obj(tag) + objcount++ + } + + // self-verification + if count := p.int(); count != objcount { + errorf("got %d objects; want %d", objcount, count) + } + + // ignore compiler-specific import data + + // complete interfaces + // TODO(gri) re-investigate if we still need to do this in a delayed fashion + for _, typ := range p.interfaceList { + typ.Complete() + } + + // record all referenced packages as imports + list := append(([]*types.Package)(nil), p.pkgList[1:]...) + sort.Sort(byPath(list)) + pkg.SetImports(list) + + // package was imported completely and without errors + pkg.MarkComplete() + + return p.read, pkg, nil +} + +func errorf(format string, args ...interface{}) { + panic(fmt.Sprintf(format, args...)) +} + +func (p *importer) pkg() *types.Package { + // if the package was seen before, i is its index (>= 0) + i := p.tagOrIndex() + if i >= 0 { + return p.pkgList[i] + } + + // otherwise, i is the package tag (< 0) + if i != packageTag { + errorf("unexpected package tag %d version %d", i, p.version) + } + + // read package data + name := p.string() + var path string + if p.version >= 5 { + path = p.path() + } else { + path = p.string() + } + if p.version >= 6 { + p.int() // package height; unused by go/types + } + + // we should never see an empty package name + if name == "" { + errorf("empty package name in import") + } + + // an empty path denotes the package we are currently importing; + // it must be the first package we see + if (path == "") != (len(p.pkgList) == 0) { + errorf("package path %q for pkg index %d", path, len(p.pkgList)) + } + + // if the package was imported before, use that one; otherwise create a new one + if path == "" { + path = p.importpath + } + pkg := p.imports[path] + if pkg == nil { + pkg = types.NewPackage(path, name) + p.imports[path] = pkg + } else if pkg.Name() != name { + errorf("conflicting names %s and %s for package %q", pkg.Name(), name, path) + } + p.pkgList = append(p.pkgList, pkg) + + return pkg +} + +// objTag returns the tag value for each object kind. +func objTag(obj types.Object) int { + switch obj.(type) { + case *types.Const: + return constTag + case *types.TypeName: + return typeTag + case *types.Var: + return varTag + case *types.Func: + return funcTag + default: + errorf("unexpected object: %v (%T)", obj, obj) // panics + panic("unreachable") + } +} + +func sameObj(a, b types.Object) bool { + // Because unnamed types are not canonicalized, we cannot simply compare types for + // (pointer) identity. + // Ideally we'd check equality of constant values as well, but this is good enough. + return objTag(a) == objTag(b) && types.Identical(a.Type(), b.Type()) +} + +func (p *importer) declare(obj types.Object) { + pkg := obj.Pkg() + if alt := pkg.Scope().Insert(obj); alt != nil { + // This can only trigger if we import a (non-type) object a second time. + // Excluding type aliases, this cannot happen because 1) we only import a package + // once; and b) we ignore compiler-specific export data which may contain + // functions whose inlined function bodies refer to other functions that + // were already imported. + // However, type aliases require reexporting the original type, so we need + // to allow it (see also the comment in cmd/compile/internal/gc/bimport.go, + // method importer.obj, switch case importing functions). + // TODO(gri) review/update this comment once the gc compiler handles type aliases. + if !sameObj(obj, alt) { + errorf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", obj, alt) + } + } +} + +func (p *importer) obj(tag int) { + switch tag { + case constTag: + pos := p.pos() + pkg, name := p.qualifiedName() + typ := p.typ(nil, nil) + val := p.value() + p.declare(types.NewConst(pos, pkg, name, typ, val)) + + case aliasTag: + // TODO(gri) verify type alias hookup is correct + pos := p.pos() + pkg, name := p.qualifiedName() + typ := p.typ(nil, nil) + p.declare(types.NewTypeName(pos, pkg, name, typ)) + + case typeTag: + p.typ(nil, nil) + + case varTag: + pos := p.pos() + pkg, name := p.qualifiedName() + typ := p.typ(nil, nil) + p.declare(types.NewVar(pos, pkg, name, typ)) + + case funcTag: + pos := p.pos() + pkg, name := p.qualifiedName() + params, isddd := p.paramList() + result, _ := p.paramList() + sig := types.NewSignature(nil, params, result, isddd) + p.declare(types.NewFunc(pos, pkg, name, sig)) + + default: + errorf("unexpected object tag %d", tag) + } +} + +const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go + +func (p *importer) pos() token.Pos { + if !p.posInfoFormat { + return token.NoPos + } + + file := p.prevFile + line := p.prevLine + delta := p.int() + line += delta + if p.version >= 5 { + if delta == deltaNewFile { + if n := p.int(); n >= 0 { + // file changed + file = p.path() + line = n + } + } + } else { + if delta == 0 { + if n := p.int(); n >= 0 { + // file changed + file = p.prevFile[:n] + p.string() + line = p.int() + } + } + } + p.prevFile = file + p.prevLine = line + + return p.fake.pos(file, line, 0) +} + +// Synthesize a token.Pos +type fakeFileSet struct { + fset *token.FileSet + files map[string]*token.File +} + +func (s *fakeFileSet) pos(file string, line, column int) token.Pos { + // TODO(mdempsky): Make use of column. + + // Since we don't know the set of needed file positions, we + // reserve maxlines positions per file. + const maxlines = 64 * 1024 + f := s.files[file] + if f == nil { + f = s.fset.AddFile(file, -1, maxlines) + s.files[file] = f + // Allocate the fake linebreak indices on first use. + // TODO(adonovan): opt: save ~512KB using a more complex scheme? + fakeLinesOnce.Do(func() { + fakeLines = make([]int, maxlines) + for i := range fakeLines { + fakeLines[i] = i + } + }) + f.SetLines(fakeLines) + } + + if line > maxlines { + line = 1 + } + + // Treat the file as if it contained only newlines + // and column=1: use the line number as the offset. + return f.Pos(line - 1) +} + +var ( + fakeLines []int + fakeLinesOnce sync.Once +) + +func (p *importer) qualifiedName() (pkg *types.Package, name string) { + name = p.string() + pkg = p.pkg() + return +} + +func (p *importer) record(t types.Type) { + p.typList = append(p.typList, t) +} + +// A dddSlice is a types.Type representing ...T parameters. +// It only appears for parameter types and does not escape +// the importer. +type dddSlice struct { + elem types.Type +} + +func (t *dddSlice) Underlying() types.Type { return t } +func (t *dddSlice) String() string { return "..." + t.elem.String() } + +// parent is the package which declared the type; parent == nil means +// the package currently imported. The parent package is needed for +// exported struct fields and interface methods which don't contain +// explicit package information in the export data. +// +// A non-nil tname is used as the "owner" of the result type; i.e., +// the result type is the underlying type of tname. tname is used +// to give interface methods a named receiver type where possible. +func (p *importer) typ(parent *types.Package, tname *types.Named) types.Type { + // if the type was seen before, i is its index (>= 0) + i := p.tagOrIndex() + if i >= 0 { + return p.typList[i] + } + + // otherwise, i is the type tag (< 0) + switch i { + case namedTag: + // read type object + pos := p.pos() + parent, name := p.qualifiedName() + scope := parent.Scope() + obj := scope.Lookup(name) + + // if the object doesn't exist yet, create and insert it + if obj == nil { + obj = types.NewTypeName(pos, parent, name, nil) + scope.Insert(obj) + } + + if _, ok := obj.(*types.TypeName); !ok { + errorf("pkg = %s, name = %s => %s", parent, name, obj) + } + + // associate new named type with obj if it doesn't exist yet + t0 := types.NewNamed(obj.(*types.TypeName), nil, nil) + + // but record the existing type, if any + tname := obj.Type().(*types.Named) // tname is either t0 or the existing type + p.record(tname) + + // read underlying type + t0.SetUnderlying(p.typ(parent, t0)) + + // interfaces don't have associated methods + if types.IsInterface(t0) { + return tname + } + + // read associated methods + for i := p.int(); i > 0; i-- { + // TODO(gri) replace this with something closer to fieldName + pos := p.pos() + name := p.string() + if !exported(name) { + p.pkg() + } + + recv, _ := p.paramList() // TODO(gri) do we need a full param list for the receiver? + params, isddd := p.paramList() + result, _ := p.paramList() + p.int() // go:nointerface pragma - discarded + + sig := types.NewSignature(recv.At(0), params, result, isddd) + t0.AddMethod(types.NewFunc(pos, parent, name, sig)) + } + + return tname + + case arrayTag: + t := new(types.Array) + if p.trackAllTypes { + p.record(t) + } + + n := p.int64() + *t = *types.NewArray(p.typ(parent, nil), n) + return t + + case sliceTag: + t := new(types.Slice) + if p.trackAllTypes { + p.record(t) + } + + *t = *types.NewSlice(p.typ(parent, nil)) + return t + + case dddTag: + t := new(dddSlice) + if p.trackAllTypes { + p.record(t) + } + + t.elem = p.typ(parent, nil) + return t + + case structTag: + t := new(types.Struct) + if p.trackAllTypes { + p.record(t) + } + + *t = *types.NewStruct(p.fieldList(parent)) + return t + + case pointerTag: + t := new(types.Pointer) + if p.trackAllTypes { + p.record(t) + } + + *t = *types.NewPointer(p.typ(parent, nil)) + return t + + case signatureTag: + t := new(types.Signature) + if p.trackAllTypes { + p.record(t) + } + + params, isddd := p.paramList() + result, _ := p.paramList() + *t = *types.NewSignature(nil, params, result, isddd) + return t + + case interfaceTag: + // Create a dummy entry in the type list. This is safe because we + // cannot expect the interface type to appear in a cycle, as any + // such cycle must contain a named type which would have been + // first defined earlier. + // TODO(gri) Is this still true now that we have type aliases? + // See issue #23225. + n := len(p.typList) + if p.trackAllTypes { + p.record(nil) + } + + var embeddeds []types.Type + for n := p.int(); n > 0; n-- { + p.pos() + embeddeds = append(embeddeds, p.typ(parent, nil)) + } + + t := newInterface(p.methodList(parent, tname), embeddeds) + p.interfaceList = append(p.interfaceList, t) + if p.trackAllTypes { + p.typList[n] = t + } + return t + + case mapTag: + t := new(types.Map) + if p.trackAllTypes { + p.record(t) + } + + key := p.typ(parent, nil) + val := p.typ(parent, nil) + *t = *types.NewMap(key, val) + return t + + case chanTag: + t := new(types.Chan) + if p.trackAllTypes { + p.record(t) + } + + dir := chanDir(p.int()) + val := p.typ(parent, nil) + *t = *types.NewChan(dir, val) + return t + + default: + errorf("unexpected type tag %d", i) // panics + panic("unreachable") + } +} + +func chanDir(d int) types.ChanDir { + // tag values must match the constants in cmd/compile/internal/gc/go.go + switch d { + case 1 /* Crecv */ : + return types.RecvOnly + case 2 /* Csend */ : + return types.SendOnly + case 3 /* Cboth */ : + return types.SendRecv + default: + errorf("unexpected channel dir %d", d) + return 0 + } +} + +func (p *importer) fieldList(parent *types.Package) (fields []*types.Var, tags []string) { + if n := p.int(); n > 0 { + fields = make([]*types.Var, n) + tags = make([]string, n) + for i := range fields { + fields[i], tags[i] = p.field(parent) + } + } + return +} + +func (p *importer) field(parent *types.Package) (*types.Var, string) { + pos := p.pos() + pkg, name, alias := p.fieldName(parent) + typ := p.typ(parent, nil) + tag := p.string() + + anonymous := false + if name == "" { + // anonymous field - typ must be T or *T and T must be a type name + switch typ := deref(typ).(type) { + case *types.Basic: // basic types are named types + pkg = nil // // objects defined in Universe scope have no package + name = typ.Name() + case *types.Named: + name = typ.Obj().Name() + default: + errorf("named base type expected") + } + anonymous = true + } else if alias { + // anonymous field: we have an explicit name because it's an alias + anonymous = true + } + + return types.NewField(pos, pkg, name, typ, anonymous), tag +} + +func (p *importer) methodList(parent *types.Package, baseType *types.Named) (methods []*types.Func) { + if n := p.int(); n > 0 { + methods = make([]*types.Func, n) + for i := range methods { + methods[i] = p.method(parent, baseType) + } + } + return +} + +func (p *importer) method(parent *types.Package, baseType *types.Named) *types.Func { + pos := p.pos() + pkg, name, _ := p.fieldName(parent) + // If we don't have a baseType, use a nil receiver. + // A receiver using the actual interface type (which + // we don't know yet) will be filled in when we call + // types.Interface.Complete. + var recv *types.Var + if baseType != nil { + recv = types.NewVar(token.NoPos, parent, "", baseType) + } + params, isddd := p.paramList() + result, _ := p.paramList() + sig := types.NewSignature(recv, params, result, isddd) + return types.NewFunc(pos, pkg, name, sig) +} + +func (p *importer) fieldName(parent *types.Package) (pkg *types.Package, name string, alias bool) { + name = p.string() + pkg = parent + if pkg == nil { + // use the imported package instead + pkg = p.pkgList[0] + } + if p.version == 0 && name == "_" { + // version 0 didn't export a package for _ fields + return + } + switch name { + case "": + // 1) field name matches base type name and is exported: nothing to do + case "?": + // 2) field name matches base type name and is not exported: need package + name = "" + pkg = p.pkg() + case "@": + // 3) field name doesn't match type name (alias) + name = p.string() + alias = true + fallthrough + default: + if !exported(name) { + pkg = p.pkg() + } + } + return +} + +func (p *importer) paramList() (*types.Tuple, bool) { + n := p.int() + if n == 0 { + return nil, false + } + // negative length indicates unnamed parameters + named := true + if n < 0 { + n = -n + named = false + } + // n > 0 + params := make([]*types.Var, n) + isddd := false + for i := range params { + params[i], isddd = p.param(named) + } + return types.NewTuple(params...), isddd +} + +func (p *importer) param(named bool) (*types.Var, bool) { + t := p.typ(nil, nil) + td, isddd := t.(*dddSlice) + if isddd { + t = types.NewSlice(td.elem) + } + + var pkg *types.Package + var name string + if named { + name = p.string() + if name == "" { + errorf("expected named parameter") + } + if name != "_" { + pkg = p.pkg() + } + if i := strings.Index(name, "·"); i > 0 { + name = name[:i] // cut off gc-specific parameter numbering + } + } + + // read and discard compiler-specific info + p.string() + + return types.NewVar(token.NoPos, pkg, name, t), isddd +} + +func exported(name string) bool { + ch, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(ch) +} + +func (p *importer) value() constant.Value { + switch tag := p.tagOrIndex(); tag { + case falseTag: + return constant.MakeBool(false) + case trueTag: + return constant.MakeBool(true) + case int64Tag: + return constant.MakeInt64(p.int64()) + case floatTag: + return p.float() + case complexTag: + re := p.float() + im := p.float() + return constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + case stringTag: + return constant.MakeString(p.string()) + case unknownTag: + return constant.MakeUnknown() + default: + errorf("unexpected value tag %d", tag) // panics + panic("unreachable") + } +} + +func (p *importer) float() constant.Value { + sign := p.int() + if sign == 0 { + return constant.MakeInt64(0) + } + + exp := p.int() + mant := []byte(p.string()) // big endian + + // remove leading 0's if any + for len(mant) > 0 && mant[0] == 0 { + mant = mant[1:] + } + + // convert to little endian + // TODO(gri) go/constant should have a more direct conversion function + // (e.g., once it supports a big.Float based implementation) + for i, j := 0, len(mant)-1; i < j; i, j = i+1, j-1 { + mant[i], mant[j] = mant[j], mant[i] + } + + // adjust exponent (constant.MakeFromBytes creates an integer value, + // but mant represents the mantissa bits such that 0.5 <= mant < 1.0) + exp -= len(mant) << 3 + if len(mant) > 0 { + for msd := mant[len(mant)-1]; msd&0x80 == 0; msd <<= 1 { + exp++ + } + } + + x := constant.MakeFromBytes(mant) + switch { + case exp < 0: + d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp)) + x = constant.BinaryOp(x, token.QUO, d) + case exp > 0: + x = constant.Shift(x, token.SHL, uint(exp)) + } + + if sign < 0 { + x = constant.UnaryOp(token.SUB, x, 0) + } + return x +} + +// ---------------------------------------------------------------------------- +// Low-level decoders + +func (p *importer) tagOrIndex() int { + if p.debugFormat { + p.marker('t') + } + + return int(p.rawInt64()) +} + +func (p *importer) int() int { + x := p.int64() + if int64(int(x)) != x { + errorf("exported integer too large") + } + return int(x) +} + +func (p *importer) int64() int64 { + if p.debugFormat { + p.marker('i') + } + + return p.rawInt64() +} + +func (p *importer) path() string { + if p.debugFormat { + p.marker('p') + } + // if the path was seen before, i is its index (>= 0) + // (the empty string is at index 0) + i := p.rawInt64() + if i >= 0 { + return p.pathList[i] + } + // otherwise, i is the negative path length (< 0) + a := make([]string, -i) + for n := range a { + a[n] = p.string() + } + s := strings.Join(a, "/") + p.pathList = append(p.pathList, s) + return s +} + +func (p *importer) string() string { + if p.debugFormat { + p.marker('s') + } + // if the string was seen before, i is its index (>= 0) + // (the empty string is at index 0) + i := p.rawInt64() + if i >= 0 { + return p.strList[i] + } + // otherwise, i is the negative string length (< 0) + if n := int(-i); n <= cap(p.buf) { + p.buf = p.buf[:n] + } else { + p.buf = make([]byte, n) + } + for i := range p.buf { + p.buf[i] = p.rawByte() + } + s := string(p.buf) + p.strList = append(p.strList, s) + return s +} + +func (p *importer) marker(want byte) { + if got := p.rawByte(); got != want { + errorf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read) + } + + pos := p.read + if n := int(p.rawInt64()); n != pos { + errorf("incorrect position: got %d; want %d", n, pos) + } +} + +// rawInt64 should only be used by low-level decoders. +func (p *importer) rawInt64() int64 { + i, err := binary.ReadVarint(p) + if err != nil { + errorf("read error: %v", err) + } + return i +} + +// rawStringln should only be used to read the initial version string. +func (p *importer) rawStringln(b byte) string { + p.buf = p.buf[:0] + for b != '\n' { + p.buf = append(p.buf, b) + b = p.rawByte() + } + return string(p.buf) +} + +// needed for binary.ReadVarint in rawInt64 +func (p *importer) ReadByte() (byte, error) { + return p.rawByte(), nil +} + +// byte is the bottleneck interface for reading p.data. +// It unescapes '|' 'S' to '$' and '|' '|' to '|'. +// rawByte should only be used by low-level decoders. +func (p *importer) rawByte() byte { + b := p.data[0] + r := 1 + if b == '|' { + b = p.data[1] + r = 2 + switch b { + case 'S': + b = '$' + case '|': + // nothing to do + default: + errorf("unexpected escape sequence in export data") + } + } + p.data = p.data[r:] + p.read += r + return b + +} + +// ---------------------------------------------------------------------------- +// Export format + +// Tags. Must be < 0. +const ( + // Objects + packageTag = -(iota + 1) + constTag + typeTag + varTag + funcTag + endTag + + // Types + namedTag + arrayTag + sliceTag + dddTag + structTag + pointerTag + signatureTag + interfaceTag + mapTag + chanTag + + // Values + falseTag + trueTag + int64Tag + floatTag + fractionTag // not used by gc + complexTag + stringTag + nilTag // only used by gc (appears in exported inlined function bodies) + unknownTag // not used by gc (only appears in packages with errors) + + // Type aliases + aliasTag +) + +var predeclOnce sync.Once +var predecl []types.Type // initialized lazily + +func predeclared() []types.Type { + predeclOnce.Do(func() { + // initialize lazily to be sure that all + // elements have been initialized before + predecl = []types.Type{ // basic types + types.Typ[types.Bool], + types.Typ[types.Int], + types.Typ[types.Int8], + types.Typ[types.Int16], + types.Typ[types.Int32], + types.Typ[types.Int64], + types.Typ[types.Uint], + types.Typ[types.Uint8], + types.Typ[types.Uint16], + types.Typ[types.Uint32], + types.Typ[types.Uint64], + types.Typ[types.Uintptr], + types.Typ[types.Float32], + types.Typ[types.Float64], + types.Typ[types.Complex64], + types.Typ[types.Complex128], + types.Typ[types.String], + + // basic type aliases + types.Universe.Lookup("byte").Type(), + types.Universe.Lookup("rune").Type(), + + // error + types.Universe.Lookup("error").Type(), + + // untyped types + types.Typ[types.UntypedBool], + types.Typ[types.UntypedInt], + types.Typ[types.UntypedRune], + types.Typ[types.UntypedFloat], + types.Typ[types.UntypedComplex], + types.Typ[types.UntypedString], + types.Typ[types.UntypedNil], + + // package unsafe + types.Typ[types.UnsafePointer], + + // invalid type + types.Typ[types.Invalid], // only appears in packages with errors + + // used internally by gc; never used by this package or in .a files + anyType{}, + } + }) + return predecl +} + +type anyType struct{} + +func (t anyType) Underlying() types.Type { return t } +func (t anyType) String() string { return "any" } diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/exportdata.go b/vendor/golang.org/x/tools/go/internal/gcimporter/exportdata.go new file mode 100644 index 00000000..f33dc561 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/exportdata.go @@ -0,0 +1,93 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is a copy of $GOROOT/src/go/internal/gcimporter/exportdata.go. + +// This file implements FindExportData. + +package gcimporter + +import ( + "bufio" + "fmt" + "io" + "strconv" + "strings" +) + +func readGopackHeader(r *bufio.Reader) (name string, size int, err error) { + // See $GOROOT/include/ar.h. + hdr := make([]byte, 16+12+6+6+8+10+2) + _, err = io.ReadFull(r, hdr) + if err != nil { + return + } + // leave for debugging + if false { + fmt.Printf("header: %s", hdr) + } + s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10])) + size, err = strconv.Atoi(s) + if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' { + err = fmt.Errorf("invalid archive header") + return + } + name = strings.TrimSpace(string(hdr[:16])) + return +} + +// FindExportData positions the reader r at the beginning of the +// export data section of an underlying GC-created object/archive +// file by reading from it. The reader must be positioned at the +// start of the file before calling this function. The hdr result +// is the string before the export data, either "$$" or "$$B". +// +func FindExportData(r *bufio.Reader) (hdr string, err error) { + // Read first line to make sure this is an object file. + line, err := r.ReadSlice('\n') + if err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + + if string(line) == "!\n" { + // Archive file. Scan to __.PKGDEF. + var name string + if name, _, err = readGopackHeader(r); err != nil { + return + } + + // First entry should be __.PKGDEF. + if name != "__.PKGDEF" { + err = fmt.Errorf("go archive is missing __.PKGDEF") + return + } + + // Read first line of __.PKGDEF data, so that line + // is once again the first line of the input. + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + } + + // Now at __.PKGDEF in archive or still at beginning of file. + // Either way, line should begin with "go object ". + if !strings.HasPrefix(string(line), "go object ") { + err = fmt.Errorf("not a Go object file") + return + } + + // Skip over object header to export data. + // Begins after first line starting with $$. + for line[0] != '$' { + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + } + hdr = string(line) + + return +} diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go b/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go new file mode 100644 index 00000000..9cf18660 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go @@ -0,0 +1,1078 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is a modified copy of $GOROOT/src/go/internal/gcimporter/gcimporter.go, +// but it also contains the original source-based importer code for Go1.6. +// Once we stop supporting 1.6, we can remove that code. + +// Package gcimporter provides various functions for reading +// gc-generated object files that can be used to implement the +// Importer interface defined by the Go 1.5 standard library package. +package gcimporter // import "golang.org/x/tools/go/internal/gcimporter" + +import ( + "bufio" + "errors" + "fmt" + "go/build" + "go/constant" + "go/token" + "go/types" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "text/scanner" +) + +// debugging/development support +const debug = false + +var pkgExts = [...]string{".a", ".o"} + +// FindPkg returns the filename and unique package id for an import +// path based on package information provided by build.Import (using +// the build.Default build.Context). A relative srcDir is interpreted +// relative to the current working directory. +// If no file was found, an empty filename is returned. +// +func FindPkg(path, srcDir string) (filename, id string) { + if path == "" { + return + } + + var noext string + switch { + default: + // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" + // Don't require the source files to be present. + if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 + srcDir = abs + } + bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) + if bp.PkgObj == "" { + id = path // make sure we have an id to print in error message + return + } + noext = strings.TrimSuffix(bp.PkgObj, ".a") + id = bp.ImportPath + + case build.IsLocalImport(path): + // "./x" -> "/this/directory/x.ext", "/this/directory/x" + noext = filepath.Join(srcDir, path) + id = noext + + case filepath.IsAbs(path): + // for completeness only - go/build.Import + // does not support absolute imports + // "/x" -> "/x.ext", "/x" + noext = path + id = path + } + + if false { // for debugging + if path != id { + fmt.Printf("%s -> %s\n", path, id) + } + } + + // try extensions + for _, ext := range pkgExts { + filename = noext + ext + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + filename = "" // not found + return +} + +// ImportData imports a package by reading the gc-generated export data, +// adds the corresponding package object to the packages map indexed by id, +// and returns the object. +// +// The packages map must contains all packages already imported. The data +// reader position must be the beginning of the export data section. The +// filename is only used in error messages. +// +// If packages[id] contains the completely imported package, that package +// can be used directly, and there is no need to call this function (but +// there is also no harm but for extra time used). +// +func ImportData(packages map[string]*types.Package, filename, id string, data io.Reader) (pkg *types.Package, err error) { + // support for parser error handling + defer func() { + switch r := recover().(type) { + case nil: + // nothing to do + case importError: + err = r + default: + panic(r) // internal error + } + }() + + var p parser + p.init(filename, id, data, packages) + pkg = p.parseExport() + + return +} + +// Import imports a gc-generated package given its import path and srcDir, adds +// the corresponding package object to the packages map, and returns the object. +// The packages map must contain all packages already imported. +// +func Import(packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) { + var rc io.ReadCloser + var filename, id string + if lookup != nil { + // With custom lookup specified, assume that caller has + // converted path to a canonical import path for use in the map. + if path == "unsafe" { + return types.Unsafe, nil + } + id = path + + // No need to re-import if the package was imported completely before. + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + f, err := lookup(path) + if err != nil { + return nil, err + } + rc = f + } else { + filename, id = FindPkg(path, srcDir) + if filename == "" { + if path == "unsafe" { + return types.Unsafe, nil + } + return nil, fmt.Errorf("can't find import: %q", id) + } + + // no need to re-import if the package was imported completely before + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + + // open file + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + // add file name to error + err = fmt.Errorf("%s: %v", filename, err) + } + }() + rc = f + } + defer rc.Close() + + var hdr string + buf := bufio.NewReader(rc) + if hdr, err = FindExportData(buf); err != nil { + return + } + + switch hdr { + case "$$\n": + // Work-around if we don't have a filename; happens only if lookup != nil. + // Either way, the filename is only needed for importer error messages, so + // this is fine. + if filename == "" { + filename = path + } + return ImportData(packages, filename, id, buf) + + case "$$B\n": + var data []byte + data, err = ioutil.ReadAll(buf) + if err != nil { + break + } + + // TODO(gri): allow clients of go/importer to provide a FileSet. + // Or, define a new standard go/types/gcexportdata package. + fset := token.NewFileSet() + + // The indexed export format starts with an 'i'; the older + // binary export format starts with a 'c', 'd', or 'v' + // (from "version"). Select appropriate importer. + if len(data) > 0 && data[0] == 'i' { + _, pkg, err = IImportData(fset, packages, data[1:], id) + } else { + _, pkg, err = BImportData(fset, packages, data, id) + } + + default: + err = fmt.Errorf("unknown export data header: %q", hdr) + } + + return +} + +// ---------------------------------------------------------------------------- +// Parser + +// TODO(gri) Imported objects don't have position information. +// Ideally use the debug table line info; alternatively +// create some fake position (or the position of the +// import). That way error messages referring to imported +// objects can print meaningful information. + +// parser parses the exports inside a gc compiler-produced +// object/archive file and populates its scope with the results. +type parser struct { + scanner scanner.Scanner + tok rune // current token + lit string // literal string; only valid for Ident, Int, String tokens + id string // package id of imported package + sharedPkgs map[string]*types.Package // package id -> package object (across importer) + localPkgs map[string]*types.Package // package id -> package object (just this package) +} + +func (p *parser) init(filename, id string, src io.Reader, packages map[string]*types.Package) { + p.scanner.Init(src) + p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } + p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments + p.scanner.Whitespace = 1<<'\t' | 1<<' ' + p.scanner.Filename = filename // for good error messages + p.next() + p.id = id + p.sharedPkgs = packages + if debug { + // check consistency of packages map + for _, pkg := range packages { + if pkg.Name() == "" { + fmt.Printf("no package name for %s\n", pkg.Path()) + } + } + } +} + +func (p *parser) next() { + p.tok = p.scanner.Scan() + switch p.tok { + case scanner.Ident, scanner.Int, scanner.Char, scanner.String, '·': + p.lit = p.scanner.TokenText() + default: + p.lit = "" + } + if debug { + fmt.Printf("%s: %q -> %q\n", scanner.TokenString(p.tok), p.scanner.TokenText(), p.lit) + } +} + +func declTypeName(pkg *types.Package, name string) *types.TypeName { + scope := pkg.Scope() + if obj := scope.Lookup(name); obj != nil { + return obj.(*types.TypeName) + } + obj := types.NewTypeName(token.NoPos, pkg, name, nil) + // a named type may be referred to before the underlying type + // is known - set it up + types.NewNamed(obj, nil, nil) + scope.Insert(obj) + return obj +} + +// ---------------------------------------------------------------------------- +// Error handling + +// Internal errors are boxed as importErrors. +type importError struct { + pos scanner.Position + err error +} + +func (e importError) Error() string { + return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err) +} + +func (p *parser) error(err interface{}) { + if s, ok := err.(string); ok { + err = errors.New(s) + } + // panic with a runtime.Error if err is not an error + panic(importError{p.scanner.Pos(), err.(error)}) +} + +func (p *parser) errorf(format string, args ...interface{}) { + p.error(fmt.Sprintf(format, args...)) +} + +func (p *parser) expect(tok rune) string { + lit := p.lit + if p.tok != tok { + p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit) + } + p.next() + return lit +} + +func (p *parser) expectSpecial(tok string) { + sep := 'x' // not white space + i := 0 + for i < len(tok) && p.tok == rune(tok[i]) && sep > ' ' { + sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token + p.next() + i++ + } + if i < len(tok) { + p.errorf("expected %q, got %q", tok, tok[0:i]) + } +} + +func (p *parser) expectKeyword(keyword string) { + lit := p.expect(scanner.Ident) + if lit != keyword { + p.errorf("expected keyword %s, got %q", keyword, lit) + } +} + +// ---------------------------------------------------------------------------- +// Qualified and unqualified names + +// PackageId = string_lit . +// +func (p *parser) parsePackageId() string { + id, err := strconv.Unquote(p.expect(scanner.String)) + if err != nil { + p.error(err) + } + // id == "" stands for the imported package id + // (only known at time of package installation) + if id == "" { + id = p.id + } + return id +} + +// PackageName = ident . +// +func (p *parser) parsePackageName() string { + return p.expect(scanner.Ident) +} + +// dotIdentifier = ( ident | '·' ) { ident | int | '·' } . +func (p *parser) parseDotIdent() string { + ident := "" + if p.tok != scanner.Int { + sep := 'x' // not white space + for (p.tok == scanner.Ident || p.tok == scanner.Int || p.tok == '·') && sep > ' ' { + ident += p.lit + sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token + p.next() + } + } + if ident == "" { + p.expect(scanner.Ident) // use expect() for error handling + } + return ident +} + +// QualifiedName = "@" PackageId "." ( "?" | dotIdentifier ) . +// +func (p *parser) parseQualifiedName() (id, name string) { + p.expect('@') + id = p.parsePackageId() + p.expect('.') + // Per rev f280b8a485fd (10/2/2013), qualified names may be used for anonymous fields. + if p.tok == '?' { + p.next() + } else { + name = p.parseDotIdent() + } + return +} + +// getPkg returns the package for a given id. If the package is +// not found, create the package and add it to the p.localPkgs +// and p.sharedPkgs maps. name is the (expected) name of the +// package. If name == "", the package name is expected to be +// set later via an import clause in the export data. +// +// id identifies a package, usually by a canonical package path like +// "encoding/json" but possibly by a non-canonical import path like +// "./json". +// +func (p *parser) getPkg(id, name string) *types.Package { + // package unsafe is not in the packages maps - handle explicitly + if id == "unsafe" { + return types.Unsafe + } + + pkg := p.localPkgs[id] + if pkg == nil { + // first import of id from this package + pkg = p.sharedPkgs[id] + if pkg == nil { + // first import of id by this importer; + // add (possibly unnamed) pkg to shared packages + pkg = types.NewPackage(id, name) + p.sharedPkgs[id] = pkg + } + // add (possibly unnamed) pkg to local packages + if p.localPkgs == nil { + p.localPkgs = make(map[string]*types.Package) + } + p.localPkgs[id] = pkg + } else if name != "" { + // package exists already and we have an expected package name; + // make sure names match or set package name if necessary + if pname := pkg.Name(); pname == "" { + pkg.SetName(name) + } else if pname != name { + p.errorf("%s package name mismatch: %s (given) vs %s (expected)", id, pname, name) + } + } + return pkg +} + +// parseExportedName is like parseQualifiedName, but +// the package id is resolved to an imported *types.Package. +// +func (p *parser) parseExportedName() (pkg *types.Package, name string) { + id, name := p.parseQualifiedName() + pkg = p.getPkg(id, "") + return +} + +// ---------------------------------------------------------------------------- +// Types + +// BasicType = identifier . +// +func (p *parser) parseBasicType() types.Type { + id := p.expect(scanner.Ident) + obj := types.Universe.Lookup(id) + if obj, ok := obj.(*types.TypeName); ok { + return obj.Type() + } + p.errorf("not a basic type: %s", id) + return nil +} + +// ArrayType = "[" int_lit "]" Type . +// +func (p *parser) parseArrayType(parent *types.Package) types.Type { + // "[" already consumed and lookahead known not to be "]" + lit := p.expect(scanner.Int) + p.expect(']') + elem := p.parseType(parent) + n, err := strconv.ParseInt(lit, 10, 64) + if err != nil { + p.error(err) + } + return types.NewArray(elem, n) +} + +// MapType = "map" "[" Type "]" Type . +// +func (p *parser) parseMapType(parent *types.Package) types.Type { + p.expectKeyword("map") + p.expect('[') + key := p.parseType(parent) + p.expect(']') + elem := p.parseType(parent) + return types.NewMap(key, elem) +} + +// Name = identifier | "?" | QualifiedName . +// +// For unqualified and anonymous names, the returned package is the parent +// package unless parent == nil, in which case the returned package is the +// package being imported. (The parent package is not nil if the the name +// is an unqualified struct field or interface method name belonging to a +// type declared in another package.) +// +// For qualified names, the returned package is nil (and not created if +// it doesn't exist yet) unless materializePkg is set (which creates an +// unnamed package with valid package path). In the latter case, a +// subsequent import clause is expected to provide a name for the package. +// +func (p *parser) parseName(parent *types.Package, materializePkg bool) (pkg *types.Package, name string) { + pkg = parent + if pkg == nil { + pkg = p.sharedPkgs[p.id] + } + switch p.tok { + case scanner.Ident: + name = p.lit + p.next() + case '?': + // anonymous + p.next() + case '@': + // exported name prefixed with package path + pkg = nil + var id string + id, name = p.parseQualifiedName() + if materializePkg { + pkg = p.getPkg(id, "") + } + default: + p.error("name expected") + } + return +} + +func deref(typ types.Type) types.Type { + if p, _ := typ.(*types.Pointer); p != nil { + return p.Elem() + } + return typ +} + +// Field = Name Type [ string_lit ] . +// +func (p *parser) parseField(parent *types.Package) (*types.Var, string) { + pkg, name := p.parseName(parent, true) + + if name == "_" { + // Blank fields should be package-qualified because they + // are unexported identifiers, but gc does not qualify them. + // Assuming that the ident belongs to the current package + // causes types to change during re-exporting, leading + // to spurious "can't assign A to B" errors from go/types. + // As a workaround, pretend all blank fields belong + // to the same unique dummy package. + const blankpkg = "<_>" + pkg = p.getPkg(blankpkg, blankpkg) + } + + typ := p.parseType(parent) + anonymous := false + if name == "" { + // anonymous field - typ must be T or *T and T must be a type name + switch typ := deref(typ).(type) { + case *types.Basic: // basic types are named types + pkg = nil // objects defined in Universe scope have no package + name = typ.Name() + case *types.Named: + name = typ.Obj().Name() + default: + p.errorf("anonymous field expected") + } + anonymous = true + } + tag := "" + if p.tok == scanner.String { + s := p.expect(scanner.String) + var err error + tag, err = strconv.Unquote(s) + if err != nil { + p.errorf("invalid struct tag %s: %s", s, err) + } + } + return types.NewField(token.NoPos, pkg, name, typ, anonymous), tag +} + +// StructType = "struct" "{" [ FieldList ] "}" . +// FieldList = Field { ";" Field } . +// +func (p *parser) parseStructType(parent *types.Package) types.Type { + var fields []*types.Var + var tags []string + + p.expectKeyword("struct") + p.expect('{') + for i := 0; p.tok != '}' && p.tok != scanner.EOF; i++ { + if i > 0 { + p.expect(';') + } + fld, tag := p.parseField(parent) + if tag != "" && tags == nil { + tags = make([]string, i) + } + if tags != nil { + tags = append(tags, tag) + } + fields = append(fields, fld) + } + p.expect('}') + + return types.NewStruct(fields, tags) +} + +// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] . +// +func (p *parser) parseParameter() (par *types.Var, isVariadic bool) { + _, name := p.parseName(nil, false) + // remove gc-specific parameter numbering + if i := strings.Index(name, "·"); i >= 0 { + name = name[:i] + } + if p.tok == '.' { + p.expectSpecial("...") + isVariadic = true + } + typ := p.parseType(nil) + if isVariadic { + typ = types.NewSlice(typ) + } + // ignore argument tag (e.g. "noescape") + if p.tok == scanner.String { + p.next() + } + // TODO(gri) should we provide a package? + par = types.NewVar(token.NoPos, nil, name, typ) + return +} + +// Parameters = "(" [ ParameterList ] ")" . +// ParameterList = { Parameter "," } Parameter . +// +func (p *parser) parseParameters() (list []*types.Var, isVariadic bool) { + p.expect('(') + for p.tok != ')' && p.tok != scanner.EOF { + if len(list) > 0 { + p.expect(',') + } + par, variadic := p.parseParameter() + list = append(list, par) + if variadic { + if isVariadic { + p.error("... not on final argument") + } + isVariadic = true + } + } + p.expect(')') + + return +} + +// Signature = Parameters [ Result ] . +// Result = Type | Parameters . +// +func (p *parser) parseSignature(recv *types.Var) *types.Signature { + params, isVariadic := p.parseParameters() + + // optional result type + var results []*types.Var + if p.tok == '(' { + var variadic bool + results, variadic = p.parseParameters() + if variadic { + p.error("... not permitted on result type") + } + } + + return types.NewSignature(recv, types.NewTuple(params...), types.NewTuple(results...), isVariadic) +} + +// InterfaceType = "interface" "{" [ MethodList ] "}" . +// MethodList = Method { ";" Method } . +// Method = Name Signature . +// +// The methods of embedded interfaces are always "inlined" +// by the compiler and thus embedded interfaces are never +// visible in the export data. +// +func (p *parser) parseInterfaceType(parent *types.Package) types.Type { + var methods []*types.Func + + p.expectKeyword("interface") + p.expect('{') + for i := 0; p.tok != '}' && p.tok != scanner.EOF; i++ { + if i > 0 { + p.expect(';') + } + pkg, name := p.parseName(parent, true) + sig := p.parseSignature(nil) + methods = append(methods, types.NewFunc(token.NoPos, pkg, name, sig)) + } + p.expect('}') + + // Complete requires the type's embedded interfaces to be fully defined, + // but we do not define any + return types.NewInterface(methods, nil).Complete() +} + +// ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type . +// +func (p *parser) parseChanType(parent *types.Package) types.Type { + dir := types.SendRecv + if p.tok == scanner.Ident { + p.expectKeyword("chan") + if p.tok == '<' { + p.expectSpecial("<-") + dir = types.SendOnly + } + } else { + p.expectSpecial("<-") + p.expectKeyword("chan") + dir = types.RecvOnly + } + elem := p.parseType(parent) + return types.NewChan(dir, elem) +} + +// Type = +// BasicType | TypeName | ArrayType | SliceType | StructType | +// PointerType | FuncType | InterfaceType | MapType | ChanType | +// "(" Type ")" . +// +// BasicType = ident . +// TypeName = ExportedName . +// SliceType = "[" "]" Type . +// PointerType = "*" Type . +// FuncType = "func" Signature . +// +func (p *parser) parseType(parent *types.Package) types.Type { + switch p.tok { + case scanner.Ident: + switch p.lit { + default: + return p.parseBasicType() + case "struct": + return p.parseStructType(parent) + case "func": + // FuncType + p.next() + return p.parseSignature(nil) + case "interface": + return p.parseInterfaceType(parent) + case "map": + return p.parseMapType(parent) + case "chan": + return p.parseChanType(parent) + } + case '@': + // TypeName + pkg, name := p.parseExportedName() + return declTypeName(pkg, name).Type() + case '[': + p.next() // look ahead + if p.tok == ']' { + // SliceType + p.next() + return types.NewSlice(p.parseType(parent)) + } + return p.parseArrayType(parent) + case '*': + // PointerType + p.next() + return types.NewPointer(p.parseType(parent)) + case '<': + return p.parseChanType(parent) + case '(': + // "(" Type ")" + p.next() + typ := p.parseType(parent) + p.expect(')') + return typ + } + p.errorf("expected type, got %s (%q)", scanner.TokenString(p.tok), p.lit) + return nil +} + +// ---------------------------------------------------------------------------- +// Declarations + +// ImportDecl = "import" PackageName PackageId . +// +func (p *parser) parseImportDecl() { + p.expectKeyword("import") + name := p.parsePackageName() + p.getPkg(p.parsePackageId(), name) +} + +// int_lit = [ "+" | "-" ] { "0" ... "9" } . +// +func (p *parser) parseInt() string { + s := "" + switch p.tok { + case '-': + s = "-" + p.next() + case '+': + p.next() + } + return s + p.expect(scanner.Int) +} + +// number = int_lit [ "p" int_lit ] . +// +func (p *parser) parseNumber() (typ *types.Basic, val constant.Value) { + // mantissa + mant := constant.MakeFromLiteral(p.parseInt(), token.INT, 0) + if mant == nil { + panic("invalid mantissa") + } + + if p.lit == "p" { + // exponent (base 2) + p.next() + exp, err := strconv.ParseInt(p.parseInt(), 10, 0) + if err != nil { + p.error(err) + } + if exp < 0 { + denom := constant.MakeInt64(1) + denom = constant.Shift(denom, token.SHL, uint(-exp)) + typ = types.Typ[types.UntypedFloat] + val = constant.BinaryOp(mant, token.QUO, denom) + return + } + if exp > 0 { + mant = constant.Shift(mant, token.SHL, uint(exp)) + } + typ = types.Typ[types.UntypedFloat] + val = mant + return + } + + typ = types.Typ[types.UntypedInt] + val = mant + return +} + +// ConstDecl = "const" ExportedName [ Type ] "=" Literal . +// Literal = bool_lit | int_lit | float_lit | complex_lit | rune_lit | string_lit . +// bool_lit = "true" | "false" . +// complex_lit = "(" float_lit "+" float_lit "i" ")" . +// rune_lit = "(" int_lit "+" int_lit ")" . +// string_lit = `"` { unicode_char } `"` . +// +func (p *parser) parseConstDecl() { + p.expectKeyword("const") + pkg, name := p.parseExportedName() + + var typ0 types.Type + if p.tok != '=' { + // constant types are never structured - no need for parent type + typ0 = p.parseType(nil) + } + + p.expect('=') + var typ types.Type + var val constant.Value + switch p.tok { + case scanner.Ident: + // bool_lit + if p.lit != "true" && p.lit != "false" { + p.error("expected true or false") + } + typ = types.Typ[types.UntypedBool] + val = constant.MakeBool(p.lit == "true") + p.next() + + case '-', scanner.Int: + // int_lit + typ, val = p.parseNumber() + + case '(': + // complex_lit or rune_lit + p.next() + if p.tok == scanner.Char { + p.next() + p.expect('+') + typ = types.Typ[types.UntypedRune] + _, val = p.parseNumber() + p.expect(')') + break + } + _, re := p.parseNumber() + p.expect('+') + _, im := p.parseNumber() + p.expectKeyword("i") + p.expect(')') + typ = types.Typ[types.UntypedComplex] + val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + + case scanner.Char: + // rune_lit + typ = types.Typ[types.UntypedRune] + val = constant.MakeFromLiteral(p.lit, token.CHAR, 0) + p.next() + + case scanner.String: + // string_lit + typ = types.Typ[types.UntypedString] + val = constant.MakeFromLiteral(p.lit, token.STRING, 0) + p.next() + + default: + p.errorf("expected literal got %s", scanner.TokenString(p.tok)) + } + + if typ0 == nil { + typ0 = typ + } + + pkg.Scope().Insert(types.NewConst(token.NoPos, pkg, name, typ0, val)) +} + +// TypeDecl = "type" ExportedName Type . +// +func (p *parser) parseTypeDecl() { + p.expectKeyword("type") + pkg, name := p.parseExportedName() + obj := declTypeName(pkg, name) + + // The type object may have been imported before and thus already + // have a type associated with it. We still need to parse the type + // structure, but throw it away if the object already has a type. + // This ensures that all imports refer to the same type object for + // a given type declaration. + typ := p.parseType(pkg) + + if name := obj.Type().(*types.Named); name.Underlying() == nil { + name.SetUnderlying(typ) + } +} + +// VarDecl = "var" ExportedName Type . +// +func (p *parser) parseVarDecl() { + p.expectKeyword("var") + pkg, name := p.parseExportedName() + typ := p.parseType(pkg) + pkg.Scope().Insert(types.NewVar(token.NoPos, pkg, name, typ)) +} + +// Func = Signature [ Body ] . +// Body = "{" ... "}" . +// +func (p *parser) parseFunc(recv *types.Var) *types.Signature { + sig := p.parseSignature(recv) + if p.tok == '{' { + p.next() + for i := 1; i > 0; p.next() { + switch p.tok { + case '{': + i++ + case '}': + i-- + } + } + } + return sig +} + +// MethodDecl = "func" Receiver Name Func . +// Receiver = "(" ( identifier | "?" ) [ "*" ] ExportedName ")" . +// +func (p *parser) parseMethodDecl() { + // "func" already consumed + p.expect('(') + recv, _ := p.parseParameter() // receiver + p.expect(')') + + // determine receiver base type object + base := deref(recv.Type()).(*types.Named) + + // parse method name, signature, and possibly inlined body + _, name := p.parseName(nil, false) + sig := p.parseFunc(recv) + + // methods always belong to the same package as the base type object + pkg := base.Obj().Pkg() + + // add method to type unless type was imported before + // and method exists already + // TODO(gri) This leads to a quadratic algorithm - ok for now because method counts are small. + base.AddMethod(types.NewFunc(token.NoPos, pkg, name, sig)) +} + +// FuncDecl = "func" ExportedName Func . +// +func (p *parser) parseFuncDecl() { + // "func" already consumed + pkg, name := p.parseExportedName() + typ := p.parseFunc(nil) + pkg.Scope().Insert(types.NewFunc(token.NoPos, pkg, name, typ)) +} + +// Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" . +// +func (p *parser) parseDecl() { + if p.tok == scanner.Ident { + switch p.lit { + case "import": + p.parseImportDecl() + case "const": + p.parseConstDecl() + case "type": + p.parseTypeDecl() + case "var": + p.parseVarDecl() + case "func": + p.next() // look ahead + if p.tok == '(' { + p.parseMethodDecl() + } else { + p.parseFuncDecl() + } + } + } + p.expect('\n') +} + +// ---------------------------------------------------------------------------- +// Export + +// Export = "PackageClause { Decl } "$$" . +// PackageClause = "package" PackageName [ "safe" ] "\n" . +// +func (p *parser) parseExport() *types.Package { + p.expectKeyword("package") + name := p.parsePackageName() + if p.tok == scanner.Ident && p.lit == "safe" { + // package was compiled with -u option - ignore + p.next() + } + p.expect('\n') + + pkg := p.getPkg(p.id, name) + + for p.tok != '$' && p.tok != scanner.EOF { + p.parseDecl() + } + + if ch := p.scanner.Peek(); p.tok != '$' || ch != '$' { + // don't call next()/expect() since reading past the + // export data may cause scanner errors (e.g. NUL chars) + p.errorf("expected '$$', got %s %c", scanner.TokenString(p.tok), ch) + } + + if n := p.scanner.ErrorCount; n != 0 { + p.errorf("expected no scanner errors, got %d", n) + } + + // Record all locally referenced packages as imports. + var imports []*types.Package + for id, pkg2 := range p.localPkgs { + if pkg2.Name() == "" { + p.errorf("%s package has no name", id) + } + if id == p.id { + continue // avoid self-edge + } + imports = append(imports, pkg2) + } + sort.Sort(byPath(imports)) + pkg.SetImports(imports) + + // package was imported completely and without errors + pkg.MarkComplete() + + return pkg +} + +type byPath []*types.Package + +func (a byPath) Len() int { return len(a) } +func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() } diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go new file mode 100644 index 00000000..4be32a2e --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go @@ -0,0 +1,739 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Indexed binary package export. +// This file was derived from $GOROOT/src/cmd/compile/internal/gc/iexport.go; +// see that file for specification of the format. + +package gcimporter + +import ( + "bytes" + "encoding/binary" + "go/ast" + "go/constant" + "go/token" + "go/types" + "io" + "math/big" + "reflect" + "sort" +) + +// Current indexed export format version. Increase with each format change. +// 0: Go1.11 encoding +const iexportVersion = 0 + +// IExportData returns the binary export data for pkg. +// +// If no file set is provided, position info will be missing. +// The package path of the top-level package will not be recorded, +// so that calls to IImportData can override with a provided package path. +func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) { + defer func() { + if e := recover(); e != nil { + if ierr, ok := e.(internalError); ok { + err = ierr + return + } + // Not an internal error; panic again. + panic(e) + } + }() + + p := iexporter{ + out: bytes.NewBuffer(nil), + fset: fset, + allPkgs: map[*types.Package]bool{}, + stringIndex: map[string]uint64{}, + declIndex: map[types.Object]uint64{}, + typIndex: map[types.Type]uint64{}, + localpkg: pkg, + } + + for i, pt := range predeclared() { + p.typIndex[pt] = uint64(i) + } + if len(p.typIndex) > predeclReserved { + panic(internalErrorf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved)) + } + + // Initialize work queue with exported declarations. + scope := pkg.Scope() + for _, name := range scope.Names() { + if ast.IsExported(name) { + p.pushDecl(scope.Lookup(name)) + } + } + + // Loop until no more work. + for !p.declTodo.empty() { + p.doDecl(p.declTodo.popHead()) + } + + // Append indices to data0 section. + dataLen := uint64(p.data0.Len()) + w := p.newWriter() + w.writeIndex(p.declIndex) + w.flush() + + // Assemble header. + var hdr intWriter + hdr.WriteByte('i') + hdr.uint64(iexportVersion) + hdr.uint64(uint64(p.strings.Len())) + hdr.uint64(dataLen) + + // Flush output. + io.Copy(p.out, &hdr) + io.Copy(p.out, &p.strings) + io.Copy(p.out, &p.data0) + + return p.out.Bytes(), nil +} + +// writeIndex writes out an object index. mainIndex indicates whether +// we're writing out the main index, which is also read by +// non-compiler tools and includes a complete package description +// (i.e., name and height). +func (w *exportWriter) writeIndex(index map[types.Object]uint64) { + // Build a map from packages to objects from that package. + pkgObjs := map[*types.Package][]types.Object{} + + // For the main index, make sure to include every package that + // we reference, even if we're not exporting (or reexporting) + // any symbols from it. + pkgObjs[w.p.localpkg] = nil + for pkg := range w.p.allPkgs { + pkgObjs[pkg] = nil + } + + for obj := range index { + pkgObjs[obj.Pkg()] = append(pkgObjs[obj.Pkg()], obj) + } + + var pkgs []*types.Package + for pkg, objs := range pkgObjs { + pkgs = append(pkgs, pkg) + + sort.Slice(objs, func(i, j int) bool { + return objs[i].Name() < objs[j].Name() + }) + } + + sort.Slice(pkgs, func(i, j int) bool { + return w.exportPath(pkgs[i]) < w.exportPath(pkgs[j]) + }) + + w.uint64(uint64(len(pkgs))) + for _, pkg := range pkgs { + w.string(w.exportPath(pkg)) + w.string(pkg.Name()) + w.uint64(uint64(0)) // package height is not needed for go/types + + objs := pkgObjs[pkg] + w.uint64(uint64(len(objs))) + for _, obj := range objs { + w.string(obj.Name()) + w.uint64(index[obj]) + } + } +} + +type iexporter struct { + fset *token.FileSet + out *bytes.Buffer + + localpkg *types.Package + + // allPkgs tracks all packages that have been referenced by + // the export data, so we can ensure to include them in the + // main index. + allPkgs map[*types.Package]bool + + declTodo objQueue + + strings intWriter + stringIndex map[string]uint64 + + data0 intWriter + declIndex map[types.Object]uint64 + typIndex map[types.Type]uint64 +} + +// stringOff returns the offset of s within the string section. +// If not already present, it's added to the end. +func (p *iexporter) stringOff(s string) uint64 { + off, ok := p.stringIndex[s] + if !ok { + off = uint64(p.strings.Len()) + p.stringIndex[s] = off + + p.strings.uint64(uint64(len(s))) + p.strings.WriteString(s) + } + return off +} + +// pushDecl adds n to the declaration work queue, if not already present. +func (p *iexporter) pushDecl(obj types.Object) { + // Package unsafe is known to the compiler and predeclared. + assert(obj.Pkg() != types.Unsafe) + + if _, ok := p.declIndex[obj]; ok { + return + } + + p.declIndex[obj] = ^uint64(0) // mark n present in work queue + p.declTodo.pushTail(obj) +} + +// exportWriter handles writing out individual data section chunks. +type exportWriter struct { + p *iexporter + + data intWriter + currPkg *types.Package + prevFile string + prevLine int64 +} + +func (w *exportWriter) exportPath(pkg *types.Package) string { + if pkg == w.p.localpkg { + return "" + } + return pkg.Path() +} + +func (p *iexporter) doDecl(obj types.Object) { + w := p.newWriter() + w.setPkg(obj.Pkg(), false) + + switch obj := obj.(type) { + case *types.Var: + w.tag('V') + w.pos(obj.Pos()) + w.typ(obj.Type(), obj.Pkg()) + + case *types.Func: + sig, _ := obj.Type().(*types.Signature) + if sig.Recv() != nil { + panic(internalErrorf("unexpected method: %v", sig)) + } + w.tag('F') + w.pos(obj.Pos()) + w.signature(sig) + + case *types.Const: + w.tag('C') + w.pos(obj.Pos()) + w.value(obj.Type(), obj.Val()) + + case *types.TypeName: + if obj.IsAlias() { + w.tag('A') + w.pos(obj.Pos()) + w.typ(obj.Type(), obj.Pkg()) + break + } + + // Defined type. + w.tag('T') + w.pos(obj.Pos()) + + underlying := obj.Type().Underlying() + w.typ(underlying, obj.Pkg()) + + t := obj.Type() + if types.IsInterface(t) { + break + } + + named, ok := t.(*types.Named) + if !ok { + panic(internalErrorf("%s is not a defined type", t)) + } + + n := named.NumMethods() + w.uint64(uint64(n)) + for i := 0; i < n; i++ { + m := named.Method(i) + w.pos(m.Pos()) + w.string(m.Name()) + sig, _ := m.Type().(*types.Signature) + w.param(sig.Recv()) + w.signature(sig) + } + + default: + panic(internalErrorf("unexpected object: %v", obj)) + } + + p.declIndex[obj] = w.flush() +} + +func (w *exportWriter) tag(tag byte) { + w.data.WriteByte(tag) +} + +func (w *exportWriter) pos(pos token.Pos) { + if w.p.fset == nil { + w.int64(0) + return + } + + p := w.p.fset.Position(pos) + file := p.Filename + line := int64(p.Line) + + // When file is the same as the last position (common case), + // we can save a few bytes by delta encoding just the line + // number. + // + // Note: Because data objects may be read out of order (or not + // at all), we can only apply delta encoding within a single + // object. This is handled implicitly by tracking prevFile and + // prevLine as fields of exportWriter. + + if file == w.prevFile { + delta := line - w.prevLine + w.int64(delta) + if delta == deltaNewFile { + w.int64(-1) + } + } else { + w.int64(deltaNewFile) + w.int64(line) // line >= 0 + w.string(file) + w.prevFile = file + } + w.prevLine = line +} + +func (w *exportWriter) pkg(pkg *types.Package) { + // Ensure any referenced packages are declared in the main index. + w.p.allPkgs[pkg] = true + + w.string(w.exportPath(pkg)) +} + +func (w *exportWriter) qualifiedIdent(obj types.Object) { + // Ensure any referenced declarations are written out too. + w.p.pushDecl(obj) + + w.string(obj.Name()) + w.pkg(obj.Pkg()) +} + +func (w *exportWriter) typ(t types.Type, pkg *types.Package) { + w.data.uint64(w.p.typOff(t, pkg)) +} + +func (p *iexporter) newWriter() *exportWriter { + return &exportWriter{p: p} +} + +func (w *exportWriter) flush() uint64 { + off := uint64(w.p.data0.Len()) + io.Copy(&w.p.data0, &w.data) + return off +} + +func (p *iexporter) typOff(t types.Type, pkg *types.Package) uint64 { + off, ok := p.typIndex[t] + if !ok { + w := p.newWriter() + w.doTyp(t, pkg) + off = predeclReserved + w.flush() + p.typIndex[t] = off + } + return off +} + +func (w *exportWriter) startType(k itag) { + w.data.uint64(uint64(k)) +} + +func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { + switch t := t.(type) { + case *types.Named: + w.startType(definedType) + w.qualifiedIdent(t.Obj()) + + case *types.Pointer: + w.startType(pointerType) + w.typ(t.Elem(), pkg) + + case *types.Slice: + w.startType(sliceType) + w.typ(t.Elem(), pkg) + + case *types.Array: + w.startType(arrayType) + w.uint64(uint64(t.Len())) + w.typ(t.Elem(), pkg) + + case *types.Chan: + w.startType(chanType) + // 1 RecvOnly; 2 SendOnly; 3 SendRecv + var dir uint64 + switch t.Dir() { + case types.RecvOnly: + dir = 1 + case types.SendOnly: + dir = 2 + case types.SendRecv: + dir = 3 + } + w.uint64(dir) + w.typ(t.Elem(), pkg) + + case *types.Map: + w.startType(mapType) + w.typ(t.Key(), pkg) + w.typ(t.Elem(), pkg) + + case *types.Signature: + w.startType(signatureType) + w.setPkg(pkg, true) + w.signature(t) + + case *types.Struct: + w.startType(structType) + w.setPkg(pkg, true) + + n := t.NumFields() + w.uint64(uint64(n)) + for i := 0; i < n; i++ { + f := t.Field(i) + w.pos(f.Pos()) + w.string(f.Name()) + w.typ(f.Type(), pkg) + w.bool(f.Anonymous()) + w.string(t.Tag(i)) // note (or tag) + } + + case *types.Interface: + w.startType(interfaceType) + w.setPkg(pkg, true) + + n := t.NumEmbeddeds() + w.uint64(uint64(n)) + for i := 0; i < n; i++ { + f := t.Embedded(i) + w.pos(f.Obj().Pos()) + w.typ(f.Obj().Type(), f.Obj().Pkg()) + } + + n = t.NumExplicitMethods() + w.uint64(uint64(n)) + for i := 0; i < n; i++ { + m := t.ExplicitMethod(i) + w.pos(m.Pos()) + w.string(m.Name()) + sig, _ := m.Type().(*types.Signature) + w.signature(sig) + } + + default: + panic(internalErrorf("unexpected type: %v, %v", t, reflect.TypeOf(t))) + } +} + +func (w *exportWriter) setPkg(pkg *types.Package, write bool) { + if write { + w.pkg(pkg) + } + + w.currPkg = pkg +} + +func (w *exportWriter) signature(sig *types.Signature) { + w.paramList(sig.Params()) + w.paramList(sig.Results()) + if sig.Params().Len() > 0 { + w.bool(sig.Variadic()) + } +} + +func (w *exportWriter) paramList(tup *types.Tuple) { + n := tup.Len() + w.uint64(uint64(n)) + for i := 0; i < n; i++ { + w.param(tup.At(i)) + } +} + +func (w *exportWriter) param(obj types.Object) { + w.pos(obj.Pos()) + w.localIdent(obj) + w.typ(obj.Type(), obj.Pkg()) +} + +func (w *exportWriter) value(typ types.Type, v constant.Value) { + w.typ(typ, nil) + + switch v.Kind() { + case constant.Bool: + w.bool(constant.BoolVal(v)) + case constant.Int: + var i big.Int + if i64, exact := constant.Int64Val(v); exact { + i.SetInt64(i64) + } else if ui64, exact := constant.Uint64Val(v); exact { + i.SetUint64(ui64) + } else { + i.SetString(v.ExactString(), 10) + } + w.mpint(&i, typ) + case constant.Float: + f := constantToFloat(v) + w.mpfloat(f, typ) + case constant.Complex: + w.mpfloat(constantToFloat(constant.Real(v)), typ) + w.mpfloat(constantToFloat(constant.Imag(v)), typ) + case constant.String: + w.string(constant.StringVal(v)) + case constant.Unknown: + // package contains type errors + default: + panic(internalErrorf("unexpected value %v (%T)", v, v)) + } +} + +// constantToFloat converts a constant.Value with kind constant.Float to a +// big.Float. +func constantToFloat(x constant.Value) *big.Float { + assert(x.Kind() == constant.Float) + // Use the same floating-point precision (512) as cmd/compile + // (see Mpprec in cmd/compile/internal/gc/mpfloat.go). + const mpprec = 512 + var f big.Float + f.SetPrec(mpprec) + if v, exact := constant.Float64Val(x); exact { + // float64 + f.SetFloat64(v) + } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { + // TODO(gri): add big.Rat accessor to constant.Value. + n := valueToRat(num) + d := valueToRat(denom) + f.SetRat(n.Quo(n, d)) + } else { + // Value too large to represent as a fraction => inaccessible. + // TODO(gri): add big.Float accessor to constant.Value. + _, ok := f.SetString(x.ExactString()) + assert(ok) + } + return &f +} + +// mpint exports a multi-precision integer. +// +// For unsigned types, small values are written out as a single +// byte. Larger values are written out as a length-prefixed big-endian +// byte string, where the length prefix is encoded as its complement. +// For example, bytes 0, 1, and 2 directly represent the integer +// values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-, +// 2-, and 3-byte big-endian string follow. +// +// Encoding for signed types use the same general approach as for +// unsigned types, except small values use zig-zag encoding and the +// bottom bit of length prefix byte for large values is reserved as a +// sign bit. +// +// The exact boundary between small and large encodings varies +// according to the maximum number of bytes needed to encode a value +// of type typ. As a special case, 8-bit types are always encoded as a +// single byte. +// +// TODO(mdempsky): Is this level of complexity really worthwhile? +func (w *exportWriter) mpint(x *big.Int, typ types.Type) { + basic, ok := typ.Underlying().(*types.Basic) + if !ok { + panic(internalErrorf("unexpected type %v (%T)", typ.Underlying(), typ.Underlying())) + } + + signed, maxBytes := intSize(basic) + + negative := x.Sign() < 0 + if !signed && negative { + panic(internalErrorf("negative unsigned integer; type %v, value %v", typ, x)) + } + + b := x.Bytes() + if len(b) > 0 && b[0] == 0 { + panic(internalErrorf("leading zeros")) + } + if uint(len(b)) > maxBytes { + panic(internalErrorf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x)) + } + + maxSmall := 256 - maxBytes + if signed { + maxSmall = 256 - 2*maxBytes + } + if maxBytes == 1 { + maxSmall = 256 + } + + // Check if x can use small value encoding. + if len(b) <= 1 { + var ux uint + if len(b) == 1 { + ux = uint(b[0]) + } + if signed { + ux <<= 1 + if negative { + ux-- + } + } + if ux < maxSmall { + w.data.WriteByte(byte(ux)) + return + } + } + + n := 256 - uint(len(b)) + if signed { + n = 256 - 2*uint(len(b)) + if negative { + n |= 1 + } + } + if n < maxSmall || n >= 256 { + panic(internalErrorf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n)) + } + + w.data.WriteByte(byte(n)) + w.data.Write(b) +} + +// mpfloat exports a multi-precision floating point number. +// +// The number's value is decomposed into mantissa × 2**exponent, where +// mantissa is an integer. The value is written out as mantissa (as a +// multi-precision integer) and then the exponent, except exponent is +// omitted if mantissa is zero. +func (w *exportWriter) mpfloat(f *big.Float, typ types.Type) { + if f.IsInf() { + panic("infinite constant") + } + + // Break into f = mant × 2**exp, with 0.5 <= mant < 1. + var mant big.Float + exp := int64(f.MantExp(&mant)) + + // Scale so that mant is an integer. + prec := mant.MinPrec() + mant.SetMantExp(&mant, int(prec)) + exp -= int64(prec) + + manti, acc := mant.Int(nil) + if acc != big.Exact { + panic(internalErrorf("mantissa scaling failed for %f (%s)", f, acc)) + } + w.mpint(manti, typ) + if manti.Sign() != 0 { + w.int64(exp) + } +} + +func (w *exportWriter) bool(b bool) bool { + var x uint64 + if b { + x = 1 + } + w.uint64(x) + return b +} + +func (w *exportWriter) int64(x int64) { w.data.int64(x) } +func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) } +func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) } + +func (w *exportWriter) localIdent(obj types.Object) { + // Anonymous parameters. + if obj == nil { + w.string("") + return + } + + name := obj.Name() + if name == "_" { + w.string("_") + return + } + + w.string(name) +} + +type intWriter struct { + bytes.Buffer +} + +func (w *intWriter) int64(x int64) { + var buf [binary.MaxVarintLen64]byte + n := binary.PutVarint(buf[:], x) + w.Write(buf[:n]) +} + +func (w *intWriter) uint64(x uint64) { + var buf [binary.MaxVarintLen64]byte + n := binary.PutUvarint(buf[:], x) + w.Write(buf[:n]) +} + +func assert(cond bool) { + if !cond { + panic("internal error: assertion failed") + } +} + +// The below is copied from go/src/cmd/compile/internal/gc/syntax.go. + +// objQueue is a FIFO queue of types.Object. The zero value of objQueue is +// a ready-to-use empty queue. +type objQueue struct { + ring []types.Object + head, tail int +} + +// empty returns true if q contains no Nodes. +func (q *objQueue) empty() bool { + return q.head == q.tail +} + +// pushTail appends n to the tail of the queue. +func (q *objQueue) pushTail(obj types.Object) { + if len(q.ring) == 0 { + q.ring = make([]types.Object, 16) + } else if q.head+len(q.ring) == q.tail { + // Grow the ring. + nring := make([]types.Object, len(q.ring)*2) + // Copy the old elements. + part := q.ring[q.head%len(q.ring):] + if q.tail-q.head <= len(part) { + part = part[:q.tail-q.head] + copy(nring, part) + } else { + pos := copy(nring, part) + copy(nring[pos:], q.ring[:q.tail%len(q.ring)]) + } + q.ring, q.head, q.tail = nring, 0, q.tail-q.head + } + + q.ring[q.tail%len(q.ring)] = obj + q.tail++ +} + +// popHead pops a node from the head of the queue. It panics if q is empty. +func (q *objQueue) popHead() types.Object { + if q.empty() { + panic("dequeue empty") + } + obj := q.ring[q.head%len(q.ring)] + q.head++ + return obj +} diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go new file mode 100644 index 00000000..a31a8802 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go @@ -0,0 +1,630 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Indexed package import. +// See cmd/compile/internal/gc/iexport.go for the export data format. + +// This file is a copy of $GOROOT/src/go/internal/gcimporter/iimport.go. + +package gcimporter + +import ( + "bytes" + "encoding/binary" + "fmt" + "go/constant" + "go/token" + "go/types" + "io" + "sort" +) + +type intReader struct { + *bytes.Reader + path string +} + +func (r *intReader) int64() int64 { + i, err := binary.ReadVarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +func (r *intReader) uint64() uint64 { + i, err := binary.ReadUvarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +const predeclReserved = 32 + +type itag uint64 + +const ( + // Types + definedType itag = iota + pointerType + sliceType + arrayType + chanType + mapType + signatureType + structType + interfaceType +) + +// IImportData imports a package from the serialized package data +// and returns the number of bytes consumed and a reference to the package. +// If the export data version is not recognized or the format is otherwise +// compromised, an error is returned. +func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { + const currentVersion = 1 + version := int64(-1) + defer func() { + if e := recover(); e != nil { + if version > currentVersion { + err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) + } else { + err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e) + } + } + }() + + r := &intReader{bytes.NewReader(data), path} + + version = int64(r.uint64()) + switch version { + case currentVersion, 0: + default: + errorf("unknown iexport format version %d", version) + } + + sLen := int64(r.uint64()) + dLen := int64(r.uint64()) + + whence, _ := r.Seek(0, io.SeekCurrent) + stringData := data[whence : whence+sLen] + declData := data[whence+sLen : whence+sLen+dLen] + r.Seek(sLen+dLen, io.SeekCurrent) + + p := iimporter{ + ipath: path, + version: int(version), + + stringData: stringData, + stringCache: make(map[uint64]string), + pkgCache: make(map[uint64]*types.Package), + + declData: declData, + pkgIndex: make(map[*types.Package]map[string]uint64), + typCache: make(map[uint64]types.Type), + + fake: fakeFileSet{ + fset: fset, + files: make(map[string]*token.File), + }, + } + + for i, pt := range predeclared() { + p.typCache[uint64(i)] = pt + } + + pkgList := make([]*types.Package, r.uint64()) + for i := range pkgList { + pkgPathOff := r.uint64() + pkgPath := p.stringAt(pkgPathOff) + pkgName := p.stringAt(r.uint64()) + _ = r.uint64() // package height; unused by go/types + + if pkgPath == "" { + pkgPath = path + } + pkg := imports[pkgPath] + if pkg == nil { + pkg = types.NewPackage(pkgPath, pkgName) + imports[pkgPath] = pkg + } else if pkg.Name() != pkgName { + errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path) + } + + p.pkgCache[pkgPathOff] = pkg + + nameIndex := make(map[string]uint64) + for nSyms := r.uint64(); nSyms > 0; nSyms-- { + name := p.stringAt(r.uint64()) + nameIndex[name] = r.uint64() + } + + p.pkgIndex[pkg] = nameIndex + pkgList[i] = pkg + } + if len(pkgList) == 0 { + errorf("no packages found for %s", path) + panic("unreachable") + } + p.ipkg = pkgList[0] + names := make([]string, 0, len(p.pkgIndex[p.ipkg])) + for name := range p.pkgIndex[p.ipkg] { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + p.doDecl(p.ipkg, name) + } + + for _, typ := range p.interfaceList { + typ.Complete() + } + + // record all referenced packages as imports + list := append(([]*types.Package)(nil), pkgList[1:]...) + sort.Sort(byPath(list)) + p.ipkg.SetImports(list) + + // package was imported completely and without errors + p.ipkg.MarkComplete() + + consumed, _ := r.Seek(0, io.SeekCurrent) + return int(consumed), p.ipkg, nil +} + +type iimporter struct { + ipath string + ipkg *types.Package + version int + + stringData []byte + stringCache map[uint64]string + pkgCache map[uint64]*types.Package + + declData []byte + pkgIndex map[*types.Package]map[string]uint64 + typCache map[uint64]types.Type + + fake fakeFileSet + interfaceList []*types.Interface +} + +func (p *iimporter) doDecl(pkg *types.Package, name string) { + // See if we've already imported this declaration. + if obj := pkg.Scope().Lookup(name); obj != nil { + return + } + + off, ok := p.pkgIndex[pkg][name] + if !ok { + errorf("%v.%v not in index", pkg, name) + } + + r := &importReader{p: p, currPkg: pkg} + r.declReader.Reset(p.declData[off:]) + + r.obj(name) +} + +func (p *iimporter) stringAt(off uint64) string { + if s, ok := p.stringCache[off]; ok { + return s + } + + slen, n := binary.Uvarint(p.stringData[off:]) + if n <= 0 { + errorf("varint failed") + } + spos := off + uint64(n) + s := string(p.stringData[spos : spos+slen]) + p.stringCache[off] = s + return s +} + +func (p *iimporter) pkgAt(off uint64) *types.Package { + if pkg, ok := p.pkgCache[off]; ok { + return pkg + } + path := p.stringAt(off) + if path == p.ipath { + return p.ipkg + } + errorf("missing package %q in %q", path, p.ipath) + return nil +} + +func (p *iimporter) typAt(off uint64, base *types.Named) types.Type { + if t, ok := p.typCache[off]; ok && (base == nil || !isInterface(t)) { + return t + } + + if off < predeclReserved { + errorf("predeclared type missing from cache: %v", off) + } + + r := &importReader{p: p} + r.declReader.Reset(p.declData[off-predeclReserved:]) + t := r.doType(base) + + if base == nil || !isInterface(t) { + p.typCache[off] = t + } + return t +} + +type importReader struct { + p *iimporter + declReader bytes.Reader + currPkg *types.Package + prevFile string + prevLine int64 + prevColumn int64 +} + +func (r *importReader) obj(name string) { + tag := r.byte() + pos := r.pos() + + switch tag { + case 'A': + typ := r.typ() + + r.declare(types.NewTypeName(pos, r.currPkg, name, typ)) + + case 'C': + typ, val := r.value() + + r.declare(types.NewConst(pos, r.currPkg, name, typ, val)) + + case 'F': + sig := r.signature(nil) + + r.declare(types.NewFunc(pos, r.currPkg, name, sig)) + + case 'T': + // Types can be recursive. We need to setup a stub + // declaration before recursing. + obj := types.NewTypeName(pos, r.currPkg, name, nil) + named := types.NewNamed(obj, nil, nil) + r.declare(obj) + + underlying := r.p.typAt(r.uint64(), named).Underlying() + named.SetUnderlying(underlying) + + if !isInterface(underlying) { + for n := r.uint64(); n > 0; n-- { + mpos := r.pos() + mname := r.ident() + recv := r.param() + msig := r.signature(recv) + + named.AddMethod(types.NewFunc(mpos, r.currPkg, mname, msig)) + } + } + + case 'V': + typ := r.typ() + + r.declare(types.NewVar(pos, r.currPkg, name, typ)) + + default: + errorf("unexpected tag: %v", tag) + } +} + +func (r *importReader) declare(obj types.Object) { + obj.Pkg().Scope().Insert(obj) +} + +func (r *importReader) value() (typ types.Type, val constant.Value) { + typ = r.typ() + + switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType { + case types.IsBoolean: + val = constant.MakeBool(r.bool()) + + case types.IsString: + val = constant.MakeString(r.string()) + + case types.IsInteger: + val = r.mpint(b) + + case types.IsFloat: + val = r.mpfloat(b) + + case types.IsComplex: + re := r.mpfloat(b) + im := r.mpfloat(b) + val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + + default: + if b.Kind() == types.Invalid { + val = constant.MakeUnknown() + return + } + errorf("unexpected type %v", typ) // panics + panic("unreachable") + } + + return +} + +func intSize(b *types.Basic) (signed bool, maxBytes uint) { + if (b.Info() & types.IsUntyped) != 0 { + return true, 64 + } + + switch b.Kind() { + case types.Float32, types.Complex64: + return true, 3 + case types.Float64, types.Complex128: + return true, 7 + } + + signed = (b.Info() & types.IsUnsigned) == 0 + switch b.Kind() { + case types.Int8, types.Uint8: + maxBytes = 1 + case types.Int16, types.Uint16: + maxBytes = 2 + case types.Int32, types.Uint32: + maxBytes = 4 + default: + maxBytes = 8 + } + + return +} + +func (r *importReader) mpint(b *types.Basic) constant.Value { + signed, maxBytes := intSize(b) + + maxSmall := 256 - maxBytes + if signed { + maxSmall = 256 - 2*maxBytes + } + if maxBytes == 1 { + maxSmall = 256 + } + + n, _ := r.declReader.ReadByte() + if uint(n) < maxSmall { + v := int64(n) + if signed { + v >>= 1 + if n&1 != 0 { + v = ^v + } + } + return constant.MakeInt64(v) + } + + v := -n + if signed { + v = -(n &^ 1) >> 1 + } + if v < 1 || uint(v) > maxBytes { + errorf("weird decoding: %v, %v => %v", n, signed, v) + } + + buf := make([]byte, v) + io.ReadFull(&r.declReader, buf) + + // convert to little endian + // TODO(gri) go/constant should have a more direct conversion function + // (e.g., once it supports a big.Float based implementation) + for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 { + buf[i], buf[j] = buf[j], buf[i] + } + + x := constant.MakeFromBytes(buf) + if signed && n&1 != 0 { + x = constant.UnaryOp(token.SUB, x, 0) + } + return x +} + +func (r *importReader) mpfloat(b *types.Basic) constant.Value { + x := r.mpint(b) + if constant.Sign(x) == 0 { + return x + } + + exp := r.int64() + switch { + case exp > 0: + x = constant.Shift(x, token.SHL, uint(exp)) + case exp < 0: + d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp)) + x = constant.BinaryOp(x, token.QUO, d) + } + return x +} + +func (r *importReader) ident() string { + return r.string() +} + +func (r *importReader) qualifiedIdent() (*types.Package, string) { + name := r.string() + pkg := r.pkg() + return pkg, name +} + +func (r *importReader) pos() token.Pos { + if r.p.version >= 1 { + r.posv1() + } else { + r.posv0() + } + + if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 { + return token.NoPos + } + return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn)) +} + +func (r *importReader) posv0() { + delta := r.int64() + if delta != deltaNewFile { + r.prevLine += delta + } else if l := r.int64(); l == -1 { + r.prevLine += deltaNewFile + } else { + r.prevFile = r.string() + r.prevLine = l + } +} + +func (r *importReader) posv1() { + delta := r.int64() + r.prevColumn += delta >> 1 + if delta&1 != 0 { + delta = r.int64() + r.prevLine += delta >> 1 + if delta&1 != 0 { + r.prevFile = r.string() + } + } +} + +func (r *importReader) typ() types.Type { + return r.p.typAt(r.uint64(), nil) +} + +func isInterface(t types.Type) bool { + _, ok := t.(*types.Interface) + return ok +} + +func (r *importReader) pkg() *types.Package { return r.p.pkgAt(r.uint64()) } +func (r *importReader) string() string { return r.p.stringAt(r.uint64()) } + +func (r *importReader) doType(base *types.Named) types.Type { + switch k := r.kind(); k { + default: + errorf("unexpected kind tag in %q: %v", r.p.ipath, k) + return nil + + case definedType: + pkg, name := r.qualifiedIdent() + r.p.doDecl(pkg, name) + return pkg.Scope().Lookup(name).(*types.TypeName).Type() + case pointerType: + return types.NewPointer(r.typ()) + case sliceType: + return types.NewSlice(r.typ()) + case arrayType: + n := r.uint64() + return types.NewArray(r.typ(), int64(n)) + case chanType: + dir := chanDir(int(r.uint64())) + return types.NewChan(dir, r.typ()) + case mapType: + return types.NewMap(r.typ(), r.typ()) + case signatureType: + r.currPkg = r.pkg() + return r.signature(nil) + + case structType: + r.currPkg = r.pkg() + + fields := make([]*types.Var, r.uint64()) + tags := make([]string, len(fields)) + for i := range fields { + fpos := r.pos() + fname := r.ident() + ftyp := r.typ() + emb := r.bool() + tag := r.string() + + fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb) + tags[i] = tag + } + return types.NewStruct(fields, tags) + + case interfaceType: + r.currPkg = r.pkg() + + embeddeds := make([]types.Type, r.uint64()) + for i := range embeddeds { + _ = r.pos() + embeddeds[i] = r.typ() + } + + methods := make([]*types.Func, r.uint64()) + for i := range methods { + mpos := r.pos() + mname := r.ident() + + // TODO(mdempsky): Matches bimport.go, but I + // don't agree with this. + var recv *types.Var + if base != nil { + recv = types.NewVar(token.NoPos, r.currPkg, "", base) + } + + msig := r.signature(recv) + methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig) + } + + typ := newInterface(methods, embeddeds) + r.p.interfaceList = append(r.p.interfaceList, typ) + return typ + } +} + +func (r *importReader) kind() itag { + return itag(r.uint64()) +} + +func (r *importReader) signature(recv *types.Var) *types.Signature { + params := r.paramList() + results := r.paramList() + variadic := params.Len() > 0 && r.bool() + return types.NewSignature(recv, params, results, variadic) +} + +func (r *importReader) paramList() *types.Tuple { + xs := make([]*types.Var, r.uint64()) + for i := range xs { + xs[i] = r.param() + } + return types.NewTuple(xs...) +} + +func (r *importReader) param() *types.Var { + pos := r.pos() + name := r.ident() + typ := r.typ() + return types.NewParam(pos, r.currPkg, name, typ) +} + +func (r *importReader) bool() bool { + return r.uint64() != 0 +} + +func (r *importReader) int64() int64 { + n, err := binary.ReadVarint(&r.declReader) + if err != nil { + errorf("readVarint: %v", err) + } + return n +} + +func (r *importReader) uint64() uint64 { + n, err := binary.ReadUvarint(&r.declReader) + if err != nil { + errorf("readUvarint: %v", err) + } + return n +} + +func (r *importReader) byte() byte { + x, err := r.declReader.ReadByte() + if err != nil { + errorf("declReader.ReadByte: %v", err) + } + return x +} diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go b/vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go new file mode 100644 index 00000000..463f2522 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/newInterface10.go @@ -0,0 +1,21 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.11 + +package gcimporter + +import "go/types" + +func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface { + named := make([]*types.Named, len(embeddeds)) + for i, e := range embeddeds { + var ok bool + named[i], ok = e.(*types.Named) + if !ok { + panic("embedding of non-defined interfaces in interfaces is not supported before Go 1.11") + } + } + return types.NewInterface(methods, named) +} diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go b/vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go new file mode 100644 index 00000000..ab28b95c --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/newInterface11.go @@ -0,0 +1,13 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.11 + +package gcimporter + +import "go/types" + +func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface { + return types.NewInterfaceType(methods, embeddeds) +} diff --git a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go new file mode 100644 index 00000000..ea15d57b --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go @@ -0,0 +1,173 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package packagesdriver fetches type sizes for go/packages and go/analysis. +package packagesdriver + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "go/types" + "log" + "os" + "os/exec" + "strings" + "time" +) + +var debug = false + +// GetSizes returns the sizes used by the underlying driver with the given parameters. +func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { + // TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver. + const toolPrefix = "GOPACKAGESDRIVER=" + tool := "" + for _, env := range env { + if val := strings.TrimPrefix(env, toolPrefix); val != env { + tool = val + } + } + + if tool == "" { + var err error + tool, err = exec.LookPath("gopackagesdriver") + if err != nil { + // We did not find the driver, so use "go list". + tool = "off" + } + } + + if tool == "off" { + return GetSizesGolist(ctx, buildFlags, env, dir, usesExportData) + } + + req, err := json.Marshal(struct { + Command string `json:"command"` + Env []string `json:"env"` + BuildFlags []string `json:"build_flags"` + }{ + Command: "sizes", + Env: env, + BuildFlags: buildFlags, + }) + if err != nil { + return nil, fmt.Errorf("failed to encode message to driver tool: %v", err) + } + + buf := new(bytes.Buffer) + cmd := exec.CommandContext(ctx, tool) + cmd.Dir = dir + cmd.Env = env + cmd.Stdin = bytes.NewReader(req) + cmd.Stdout = buf + cmd.Stderr = new(bytes.Buffer) + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) + } + var response struct { + // Sizes, if not nil, is the types.Sizes to use when type checking. + Sizes *types.StdSizes + } + if err := json.Unmarshal(buf.Bytes(), &response); err != nil { + return nil, err + } + return response.Sizes, nil +} + +func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { + args := []string{"list", "-f", "{{context.GOARCH}} {{context.Compiler}}"} + args = append(args, buildFlags...) + args = append(args, "--", "unsafe") + stdout, err := InvokeGo(ctx, env, dir, usesExportData, args...) + var goarch, compiler string + if err != nil { + if strings.Contains(err.Error(), "cannot find main module") { + // User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc. + // TODO(matloob): Is this a problem in practice? + envout, enverr := InvokeGo(ctx, env, dir, usesExportData, "env", "GOARCH") + if enverr != nil { + return nil, err + } + goarch = strings.TrimSpace(envout.String()) + compiler = "gc" + } else { + return nil, err + } + } else { + fields := strings.Fields(stdout.String()) + if len(fields) < 2 { + return nil, fmt.Errorf("could not determine GOARCH and Go compiler") + } + goarch = fields[0] + compiler = fields[1] + } + return types.SizesFor(compiler, goarch), nil +} + +// InvokeGo returns the stdout of a go command invocation. +func InvokeGo(ctx context.Context, env []string, dir string, usesExportData bool, args ...string) (*bytes.Buffer, error) { + if debug { + defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(env, args...)) }(time.Now()) + } + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd := exec.CommandContext(ctx, "go", args...) + // On darwin the cwd gets resolved to the real path, which breaks anything that + // expects the working directory to keep the original path, including the + // go command when dealing with modules. + // The Go stdlib has a special feature where if the cwd and the PWD are the + // same node then it trusts the PWD, so by setting it in the env for the child + // process we fix up all the paths returned by the go command. + cmd.Env = append(append([]string{}, env...), "PWD="+dir) + cmd.Dir = dir + cmd.Stdout = stdout + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + exitErr, ok := err.(*exec.ExitError) + if !ok { + // Catastrophic error: + // - executable not found + // - context cancellation + return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) + } + + // Export mode entails a build. + // If that build fails, errors appear on stderr + // (despite the -e flag) and the Export field is blank. + // Do not fail in that case. + if !usesExportData { + return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) + } + } + + // As of writing, go list -export prints some non-fatal compilation + // errors to stderr, even with -e set. We would prefer that it put + // them in the Package.Error JSON (see https://golang.org/issue/26319). + // In the meantime, there's nowhere good to put them, but they can + // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS + // is set. + if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { + fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(env, args...), stderr) + } + + // debugging + if false { + fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(env, args...), stdout) + } + + return stdout, nil +} + +func cmdDebugStr(envlist []string, args ...string) string { + env := make(map[string]string) + for _, kv := range envlist { + split := strings.Split(kv, "=") + k, v := split[0], split[1] + env[k] = v + } + + return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], args) +} diff --git a/vendor/golang.org/x/tools/go/packages/doc.go b/vendor/golang.org/x/tools/go/packages/doc.go new file mode 100644 index 00000000..3799f8ed --- /dev/null +++ b/vendor/golang.org/x/tools/go/packages/doc.go @@ -0,0 +1,222 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package packages loads Go packages for inspection and analysis. + +The Load function takes as input a list of patterns and return a list of Package +structs describing individual packages matched by those patterns. +The LoadMode controls the amount of detail in the loaded packages. + +Load passes most patterns directly to the underlying build tool, +but all patterns with the prefix "query=", where query is a +non-empty string of letters from [a-z], are reserved and may be +interpreted as query operators. + +Two query operators are currently supported: "file" and "pattern". + +The query "file=path/to/file.go" matches the package or packages enclosing +the Go source file path/to/file.go. For example "file=~/go/src/fmt/print.go" +might return the packages "fmt" and "fmt [fmt.test]". + +The query "pattern=string" causes "string" to be passed directly to +the underlying build tool. In most cases this is unnecessary, +but an application can use Load("pattern=" + x) as an escaping mechanism +to ensure that x is not interpreted as a query operator if it contains '='. + +All other query operators are reserved for future use and currently +cause Load to report an error. + +The Package struct provides basic information about the package, including + + - ID, a unique identifier for the package in the returned set; + - GoFiles, the names of the package's Go source files; + - Imports, a map from source import strings to the Packages they name; + - Types, the type information for the package's exported symbols; + - Syntax, the parsed syntax trees for the package's source code; and + - TypeInfo, the result of a complete type-check of the package syntax trees. + +(See the documentation for type Package for the complete list of fields +and more detailed descriptions.) + +For example, + + Load(nil, "bytes", "unicode...") + +returns four Package structs describing the standard library packages +bytes, unicode, unicode/utf16, and unicode/utf8. Note that one pattern +can match multiple packages and that a package might be matched by +multiple patterns: in general it is not possible to determine which +packages correspond to which patterns. + +Note that the list returned by Load contains only the packages matched +by the patterns. Their dependencies can be found by walking the import +graph using the Imports fields. + +The Load function can be configured by passing a pointer to a Config as +the first argument. A nil Config is equivalent to the zero Config, which +causes Load to run in LoadFiles mode, collecting minimal information. +See the documentation for type Config for details. + +As noted earlier, the Config.Mode controls the amount of detail +reported about the loaded packages, with each mode returning all the data of the +previous mode with some extra added. See the documentation for type LoadMode +for details. + +Most tools should pass their command-line arguments (after any flags) +uninterpreted to the loader, so that the loader can interpret them +according to the conventions of the underlying build system. +See the Example function for typical usage. + +*/ +package packages // import "golang.org/x/tools/go/packages" + +/* + +Motivation and design considerations + +The new package's design solves problems addressed by two existing +packages: go/build, which locates and describes packages, and +golang.org/x/tools/go/loader, which loads, parses and type-checks them. +The go/build.Package structure encodes too much of the 'go build' way +of organizing projects, leaving us in need of a data type that describes a +package of Go source code independent of the underlying build system. +We wanted something that works equally well with go build and vgo, and +also other build systems such as Bazel and Blaze, making it possible to +construct analysis tools that work in all these environments. +Tools such as errcheck and staticcheck were essentially unavailable to +the Go community at Google, and some of Google's internal tools for Go +are unavailable externally. +This new package provides a uniform way to obtain package metadata by +querying each of these build systems, optionally supporting their +preferred command-line notations for packages, so that tools integrate +neatly with users' build environments. The Metadata query function +executes an external query tool appropriate to the current workspace. + +Loading packages always returns the complete import graph "all the way down", +even if all you want is information about a single package, because the query +mechanisms of all the build systems we currently support ({go,vgo} list, and +blaze/bazel aspect-based query) cannot provide detailed information +about one package without visiting all its dependencies too, so there is +no additional asymptotic cost to providing transitive information. +(This property might not be true of a hypothetical 5th build system.) + +In calls to TypeCheck, all initial packages, and any package that +transitively depends on one of them, must be loaded from source. +Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from +source; D may be loaded from export data, and E may not be loaded at all +(though it's possible that D's export data mentions it, so a +types.Package may be created for it and exposed.) + +The old loader had a feature to suppress type-checking of function +bodies on a per-package basis, primarily intended to reduce the work of +obtaining type information for imported packages. Now that imports are +satisfied by export data, the optimization no longer seems necessary. + +Despite some early attempts, the old loader did not exploit export data, +instead always using the equivalent of WholeProgram mode. This was due +to the complexity of mixing source and export data packages (now +resolved by the upward traversal mentioned above), and because export data +files were nearly always missing or stale. Now that 'go build' supports +caching, all the underlying build systems can guarantee to produce +export data in a reasonable (amortized) time. + +Test "main" packages synthesized by the build system are now reported as +first-class packages, avoiding the need for clients (such as go/ssa) to +reinvent this generation logic. + +One way in which go/packages is simpler than the old loader is in its +treatment of in-package tests. In-package tests are packages that +consist of all the files of the library under test, plus the test files. +The old loader constructed in-package tests by a two-phase process of +mutation called "augmentation": first it would construct and type check +all the ordinary library packages and type-check the packages that +depend on them; then it would add more (test) files to the package and +type-check again. This two-phase approach had four major problems: +1) in processing the tests, the loader modified the library package, + leaving no way for a client application to see both the test + package and the library package; one would mutate into the other. +2) because test files can declare additional methods on types defined in + the library portion of the package, the dispatch of method calls in + the library portion was affected by the presence of the test files. + This should have been a clue that the packages were logically + different. +3) this model of "augmentation" assumed at most one in-package test + per library package, which is true of projects using 'go build', + but not other build systems. +4) because of the two-phase nature of test processing, all packages that + import the library package had to be processed before augmentation, + forcing a "one-shot" API and preventing the client from calling Load + in several times in sequence as is now possible in WholeProgram mode. + (TypeCheck mode has a similar one-shot restriction for a different reason.) + +Early drafts of this package supported "multi-shot" operation. +Although it allowed clients to make a sequence of calls (or concurrent +calls) to Load, building up the graph of Packages incrementally, +it was of marginal value: it complicated the API +(since it allowed some options to vary across calls but not others), +it complicated the implementation, +it cannot be made to work in Types mode, as explained above, +and it was less efficient than making one combined call (when this is possible). +Among the clients we have inspected, none made multiple calls to load +but could not be easily and satisfactorily modified to make only a single call. +However, applications changes may be required. +For example, the ssadump command loads the user-specified packages +and in addition the runtime package. It is tempting to simply append +"runtime" to the user-provided list, but that does not work if the user +specified an ad-hoc package such as [a.go b.go]. +Instead, ssadump no longer requests the runtime package, +but seeks it among the dependencies of the user-specified packages, +and emits an error if it is not found. + +Overlays: The Overlay field in the Config allows providing alternate contents +for Go source files, by providing a mapping from file path to contents. +go/packages will pull in new imports added in overlay files when go/packages +is run in LoadImports mode or greater. +Overlay support for the go list driver isn't complete yet: if the file doesn't +exist on disk, it will only be recognized in an overlay if it is a non-test file +and the package would be reported even without the overlay. + +Questions & Tasks + +- Add GOARCH/GOOS? + They are not portable concepts, but could be made portable. + Our goal has been to allow users to express themselves using the conventions + of the underlying build system: if the build system honors GOARCH + during a build and during a metadata query, then so should + applications built atop that query mechanism. + Conversely, if the target architecture of the build is determined by + command-line flags, the application can pass the relevant + flags through to the build system using a command such as: + myapp -query_flag="--cpu=amd64" -query_flag="--os=darwin" + However, this approach is low-level, unwieldy, and non-portable. + GOOS and GOARCH seem important enough to warrant a dedicated option. + +- How should we handle partial failures such as a mixture of good and + malformed patterns, existing and non-existent packages, successful and + failed builds, import failures, import cycles, and so on, in a call to + Load? + +- Support bazel, blaze, and go1.10 list, not just go1.11 list. + +- Handle (and test) various partial success cases, e.g. + a mixture of good packages and: + invalid patterns + nonexistent packages + empty packages + packages with malformed package or import declarations + unreadable files + import cycles + other parse errors + type errors + Make sure we record errors at the correct place in the graph. + +- Missing packages among initial arguments are not reported. + Return bogus packages for them, like golist does. + +- "undeclared name" errors (for example) are reported out of source file + order. I suspect this is due to the breadth-first resolution now used + by go/types. Is that a bug? Discuss with gri. + +*/ diff --git a/vendor/golang.org/x/tools/go/packages/external.go b/vendor/golang.org/x/tools/go/packages/external.go new file mode 100644 index 00000000..6ac3e4f5 --- /dev/null +++ b/vendor/golang.org/x/tools/go/packages/external.go @@ -0,0 +1,100 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file enables an external tool to intercept package requests. +// If the tool is present then its results are used in preference to +// the go list command. + +package packages + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "os/exec" + "strings" +) + +// The Driver Protocol +// +// The driver, given the inputs to a call to Load, returns metadata about the packages specified. +// This allows for different build systems to support go/packages by telling go/packages how the +// packages' source is organized. +// The driver is a binary, either specified by the GOPACKAGESDRIVER environment variable or in +// the path as gopackagesdriver. It's given the inputs to load in its argv. See the package +// documentation in doc.go for the full description of the patterns that need to be supported. +// A driver receives as a JSON-serialized driverRequest struct in standard input and will +// produce a JSON-serialized driverResponse (see definition in packages.go) in its standard output. + +// driverRequest is used to provide the portion of Load's Config that is needed by a driver. +type driverRequest struct { + Mode LoadMode `json:"mode"` + // Env specifies the environment the underlying build system should be run in. + Env []string `json:"env"` + // BuildFlags are flags that should be passed to the underlying build system. + BuildFlags []string `json:"build_flags"` + // Tests specifies whether the patterns should also return test packages. + Tests bool `json:"tests"` + // Overlay maps file paths (relative to the driver's working directory) to the byte contents + // of overlay files. + Overlay map[string][]byte `json:"overlay"` +} + +// findExternalDriver returns the file path of a tool that supplies +// the build system package structure, or "" if not found." +// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its +// value, otherwise it searches for a binary named gopackagesdriver on the PATH. +func findExternalDriver(cfg *Config) driver { + const toolPrefix = "GOPACKAGESDRIVER=" + tool := "" + for _, env := range cfg.Env { + if val := strings.TrimPrefix(env, toolPrefix); val != env { + tool = val + } + } + if tool != "" && tool == "off" { + return nil + } + if tool == "" { + var err error + tool, err = exec.LookPath("gopackagesdriver") + if err != nil { + return nil + } + } + return func(cfg *Config, words ...string) (*driverResponse, error) { + req, err := json.Marshal(driverRequest{ + Mode: cfg.Mode, + Env: cfg.Env, + BuildFlags: cfg.BuildFlags, + Tests: cfg.Tests, + Overlay: cfg.Overlay, + }) + if err != nil { + return nil, fmt.Errorf("failed to encode message to driver tool: %v", err) + } + + buf := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd := exec.CommandContext(cfg.Context, tool, words...) + cmd.Dir = cfg.Dir + cmd.Env = cfg.Env + cmd.Stdin = bytes.NewReader(req) + cmd.Stdout = buf + cmd.Stderr = stderr + if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" { + fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr) + } + + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) + } + var response driverResponse + if err := json.Unmarshal(buf.Bytes(), &response); err != nil { + return nil, err + } + return &response, nil + } +} diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go new file mode 100644 index 00000000..c581bce9 --- /dev/null +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -0,0 +1,1145 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packages + +import ( + "bytes" + "encoding/json" + "fmt" + "go/types" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "sync" + "time" + "unicode" + + "golang.org/x/tools/go/internal/packagesdriver" + "golang.org/x/tools/internal/gopathwalk" + "golang.org/x/tools/internal/semver" + "golang.org/x/tools/internal/span" +) + +// debug controls verbose logging. +var debug, _ = strconv.ParseBool(os.Getenv("GOPACKAGESDEBUG")) + +// A goTooOldError reports that the go command +// found by exec.LookPath is too old to use the new go list behavior. +type goTooOldError struct { + error +} + +// responseDeduper wraps a driverResponse, deduplicating its contents. +type responseDeduper struct { + seenRoots map[string]bool + seenPackages map[string]*Package + dr *driverResponse +} + +// init fills in r with a driverResponse. +func (r *responseDeduper) init(dr *driverResponse) { + r.dr = dr + r.seenRoots = map[string]bool{} + r.seenPackages = map[string]*Package{} + for _, pkg := range dr.Packages { + r.seenPackages[pkg.ID] = pkg + } + for _, root := range dr.Roots { + r.seenRoots[root] = true + } +} + +func (r *responseDeduper) addPackage(p *Package) { + if r.seenPackages[p.ID] != nil { + return + } + r.seenPackages[p.ID] = p + r.dr.Packages = append(r.dr.Packages, p) +} + +func (r *responseDeduper) addRoot(id string) { + if r.seenRoots[id] { + return + } + r.seenRoots[id] = true + r.dr.Roots = append(r.dr.Roots, id) +} + +// goInfo contains global information from the go tool. +type goInfo struct { + rootDirs map[string]string + env goEnv +} + +type goEnv struct { + modulesOn bool +} + +func determineEnv(cfg *Config) goEnv { + buf, err := invokeGo(cfg, "env", "GOMOD") + if err != nil { + return goEnv{} + } + gomod := bytes.TrimSpace(buf.Bytes()) + + env := goEnv{} + env.modulesOn = len(gomod) > 0 + return env +} + +// goListDriver uses the go list command to interpret the patterns and produce +// the build system package structure. +// See driver for more details. +func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { + var sizes types.Sizes + var sizeserr error + var sizeswg sync.WaitGroup + if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { + sizeswg.Add(1) + go func() { + sizes, sizeserr = getSizes(cfg) + sizeswg.Done() + }() + } + defer sizeswg.Wait() + + // start fetching rootDirs + var info goInfo + var rootDirsReady, envReady = make(chan struct{}), make(chan struct{}) + go func() { + info.rootDirs = determineRootDirs(cfg) + close(rootDirsReady) + }() + go func() { + info.env = determineEnv(cfg) + close(envReady) + }() + getGoInfo := func() *goInfo { + <-rootDirsReady + <-envReady + return &info + } + + // Ensure that we don't leak goroutines: Load is synchronous, so callers will + // not expect it to access the fields of cfg after the call returns. + defer getGoInfo() + + // always pass getGoInfo to golistDriver + golistDriver := func(cfg *Config, patterns ...string) (*driverResponse, error) { + return golistDriver(cfg, getGoInfo, patterns...) + } + + // Determine files requested in contains patterns + var containFiles []string + var packagesNamed []string + restPatterns := make([]string, 0, len(patterns)) + // Extract file= and other [querytype]= patterns. Report an error if querytype + // doesn't exist. +extractQueries: + for _, pattern := range patterns { + eqidx := strings.Index(pattern, "=") + if eqidx < 0 { + restPatterns = append(restPatterns, pattern) + } else { + query, value := pattern[:eqidx], pattern[eqidx+len("="):] + switch query { + case "file": + containFiles = append(containFiles, value) + case "pattern": + restPatterns = append(restPatterns, value) + case "iamashamedtousethedisabledqueryname": + packagesNamed = append(packagesNamed, value) + case "": // not a reserved query + restPatterns = append(restPatterns, pattern) + default: + for _, rune := range query { + if rune < 'a' || rune > 'z' { // not a reserved query + restPatterns = append(restPatterns, pattern) + continue extractQueries + } + } + // Reject all other patterns containing "=" + return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern) + } + } + } + + response := &responseDeduper{} + var err error + + // See if we have any patterns to pass through to go list. Zero initial + // patterns also requires a go list call, since it's the equivalent of + // ".". + if len(restPatterns) > 0 || len(patterns) == 0 { + dr, err := golistDriver(cfg, restPatterns...) + if err != nil { + return nil, err + } + response.init(dr) + } else { + response.init(&driverResponse{}) + } + + sizeswg.Wait() + if sizeserr != nil { + return nil, sizeserr + } + // types.SizesFor always returns nil or a *types.StdSizes + response.dr.Sizes, _ = sizes.(*types.StdSizes) + + var containsCandidates []string + + if len(containFiles) != 0 { + if err := runContainsQueries(cfg, golistDriver, response, containFiles, getGoInfo); err != nil { + return nil, err + } + } + + if len(packagesNamed) != 0 { + if err := runNamedQueries(cfg, golistDriver, response, packagesNamed); err != nil { + return nil, err + } + } + + modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response, getGoInfo) + if err != nil { + return nil, err + } + if len(containFiles) > 0 { + containsCandidates = append(containsCandidates, modifiedPkgs...) + containsCandidates = append(containsCandidates, needPkgs...) + } + if err := addNeededOverlayPackages(cfg, golistDriver, response, needPkgs, getGoInfo); err != nil { + return nil, err + } + // Check candidate packages for containFiles. + if len(containFiles) > 0 { + for _, id := range containsCandidates { + pkg, ok := response.seenPackages[id] + if !ok { + response.addPackage(&Package{ + ID: id, + Errors: []Error{ + { + Kind: ListError, + Msg: fmt.Sprintf("package %s expected but not seen", id), + }, + }, + }) + continue + } + for _, f := range containFiles { + for _, g := range pkg.GoFiles { + if sameFile(f, g) { + response.addRoot(id) + } + } + } + } + } + + return response.dr, nil +} + +func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string, getGoInfo func() *goInfo) error { + if len(pkgs) == 0 { + return nil + } + drivercfg := *cfg + if getGoInfo().env.modulesOn { + drivercfg.BuildFlags = append(drivercfg.BuildFlags, "-mod=readonly") + } + dr, err := driver(&drivercfg, pkgs...) + + if err != nil { + return err + } + for _, pkg := range dr.Packages { + response.addPackage(pkg) + } + _, needPkgs, err := processGolistOverlay(cfg, response, getGoInfo) + if err != nil { + return err + } + if err := addNeededOverlayPackages(cfg, driver, response, needPkgs, getGoInfo); err != nil { + return err + } + return nil +} + +func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string, goInfo func() *goInfo) error { + for _, query := range queries { + // TODO(matloob): Do only one query per directory. + fdir := filepath.Dir(query) + // Pass absolute path of directory to go list so that it knows to treat it as a directory, + // not a package path. + pattern, err := filepath.Abs(fdir) + if err != nil { + return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) + } + dirResponse, err := driver(cfg, pattern) + if err != nil { + var queryErr error + if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil { + return err // return the original error + } + } + // `go list` can report errors for files that are not listed as part of a package's GoFiles. + // In the case of an invalid Go file, we should assume that it is part of package if only + // one package is in the response. The file may have valid contents in an overlay. + if len(dirResponse.Packages) == 1 { + pkg := dirResponse.Packages[0] + for i, err := range pkg.Errors { + s := errorSpan(err) + if !s.IsValid() { + break + } + if len(pkg.CompiledGoFiles) == 0 { + break + } + dir := filepath.Dir(pkg.CompiledGoFiles[0]) + filename := filepath.Join(dir, filepath.Base(s.URI().Filename())) + if info, err := os.Stat(filename); err != nil || info.IsDir() { + break + } + if !contains(pkg.CompiledGoFiles, filename) { + pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename) + pkg.GoFiles = append(pkg.GoFiles, filename) + pkg.Errors = append(pkg.Errors[:i], pkg.Errors[i+1:]...) + } + } + } + // A final attempt to construct an ad-hoc package. + if len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1 { + var queryErr error + if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil { + return err // return the original error + } + } + isRoot := make(map[string]bool, len(dirResponse.Roots)) + for _, root := range dirResponse.Roots { + isRoot[root] = true + } + for _, pkg := range dirResponse.Packages { + // Add any new packages to the main set + // We don't bother to filter packages that will be dropped by the changes of roots, + // that will happen anyway during graph construction outside this function. + // Over-reporting packages is not a problem. + response.addPackage(pkg) + // if the package was not a root one, it cannot have the file + if !isRoot[pkg.ID] { + continue + } + for _, pkgFile := range pkg.GoFiles { + if filepath.Base(query) == filepath.Base(pkgFile) { + response.addRoot(pkg.ID) + break + } + } + } + } + return nil +} + +// adHocPackage attempts to construct an ad-hoc package given a query that failed. +func adHocPackage(cfg *Config, driver driver, pattern, query string) (*driverResponse, error) { + // There was an error loading the package. Try to load the file as an ad-hoc package. + // Usually the error will appear in a returned package, but may not if we're in modules mode + // and the ad-hoc is located outside a module. + dirResponse, err := driver(cfg, query) + if err != nil { + return nil, err + } + // If we get nothing back from `go list`, try to make this file into its own ad-hoc package. + if len(dirResponse.Packages) == 0 && err == nil { + dirResponse.Packages = append(dirResponse.Packages, &Package{ + ID: "command-line-arguments", + PkgPath: query, + GoFiles: []string{query}, + CompiledGoFiles: []string{query}, + Imports: make(map[string]*Package), + }) + dirResponse.Roots = append(dirResponse.Roots, "command-line-arguments") + } + // Special case to handle issue #33482: + // If this is a file= query for ad-hoc packages where the file only exists on an overlay, + // and exists outside of a module, add the file in for the package. + if len(dirResponse.Packages) == 1 && (dirResponse.Packages[0].ID == "command-line-arguments" || dirResponse.Packages[0].PkgPath == filepath.ToSlash(query)) { + if len(dirResponse.Packages[0].GoFiles) == 0 { + filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath + // TODO(matloob): check if the file is outside of a root dir? + for path := range cfg.Overlay { + if path == filename { + dirResponse.Packages[0].Errors = nil + dirResponse.Packages[0].GoFiles = []string{path} + dirResponse.Packages[0].CompiledGoFiles = []string{path} + } + } + } + } + return dirResponse, nil +} + +func contains(files []string, filename string) bool { + for _, f := range files { + if f == filename { + return true + } + } + return false +} + +// errorSpan attempts to parse a standard `go list` error message +// by stripping off the trailing error message. +// +// It works only on errors whose message is prefixed by colon, +// followed by a space (": "). For example: +// +// attributes.go:13:1: expected 'package', found 'type' +// +func errorSpan(err Error) span.Span { + if err.Pos == "" { + input := strings.TrimSpace(err.Msg) + msgIndex := strings.Index(input, ": ") + if msgIndex < 0 { + return span.Parse(input) + } + return span.Parse(input[:msgIndex]) + } + return span.Parse(err.Pos) +} + +// modCacheRegexp splits a path in a module cache into module, module version, and package. +var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) + +func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error { + // calling `go env` isn't free; bail out if there's nothing to do. + if len(queries) == 0 { + return nil + } + // Determine which directories are relevant to scan. + roots, modRoot, err := roots(cfg) + if err != nil { + return err + } + + // Scan the selected directories. Simple matches, from GOPATH/GOROOT + // or the local module, can simply be "go list"ed. Matches from the + // module cache need special treatment. + var matchesMu sync.Mutex + var simpleMatches, modCacheMatches []string + add := func(root gopathwalk.Root, dir string) { + // Walk calls this concurrently; protect the result slices. + matchesMu.Lock() + defer matchesMu.Unlock() + + path := dir + if dir != root.Path { + path = dir[len(root.Path)+1:] + } + if pathMatchesQueries(path, queries) { + switch root.Type { + case gopathwalk.RootModuleCache: + modCacheMatches = append(modCacheMatches, path) + case gopathwalk.RootCurrentModule: + // We'd need to read go.mod to find the full + // import path. Relative's easier. + rel, err := filepath.Rel(cfg.Dir, dir) + if err != nil { + // This ought to be impossible, since + // we found dir in the current module. + panic(err) + } + simpleMatches = append(simpleMatches, "./"+rel) + case gopathwalk.RootGOPATH, gopathwalk.RootGOROOT: + simpleMatches = append(simpleMatches, path) + } + } + } + + startWalk := time.Now() + gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug}) + cfg.Logf("%v for walk", time.Since(startWalk)) + + // Weird special case: the top-level package in a module will be in + // whatever directory the user checked the repository out into. It's + // more reasonable for that to not match the package name. So, if there + // are any Go files in the mod root, query it just to be safe. + if modRoot != "" { + rel, err := filepath.Rel(cfg.Dir, modRoot) + if err != nil { + panic(err) // See above. + } + + files, err := ioutil.ReadDir(modRoot) + if err != nil { + panic(err) // See above. + } + + for _, f := range files { + if strings.HasSuffix(f.Name(), ".go") { + simpleMatches = append(simpleMatches, rel) + break + } + } + } + + addResponse := func(r *driverResponse) { + for _, pkg := range r.Packages { + response.addPackage(pkg) + for _, name := range queries { + if pkg.Name == name { + response.addRoot(pkg.ID) + break + } + } + } + } + + if len(simpleMatches) != 0 { + resp, err := driver(cfg, simpleMatches...) + if err != nil { + return err + } + addResponse(resp) + } + + // Module cache matches are tricky. We want to avoid downloading new + // versions of things, so we need to use the ones present in the cache. + // go list doesn't accept version specifiers, so we have to write out a + // temporary module, and do the list in that module. + if len(modCacheMatches) != 0 { + // Collect all the matches, deduplicating by major version + // and preferring the newest. + type modInfo struct { + mod string + major string + } + mods := make(map[modInfo]string) + var imports []string + for _, modPath := range modCacheMatches { + matches := modCacheRegexp.FindStringSubmatch(modPath) + mod, ver := filepath.ToSlash(matches[1]), matches[2] + importPath := filepath.ToSlash(filepath.Join(matches[1], matches[3])) + + major := semver.Major(ver) + if prevVer, ok := mods[modInfo{mod, major}]; !ok || semver.Compare(ver, prevVer) > 0 { + mods[modInfo{mod, major}] = ver + } + + imports = append(imports, importPath) + } + + // Build the temporary module. + var gomod bytes.Buffer + gomod.WriteString("module modquery\nrequire (\n") + for mod, version := range mods { + gomod.WriteString("\t" + mod.mod + " " + version + "\n") + } + gomod.WriteString(")\n") + + tmpCfg := *cfg + + // We're only trying to look at stuff in the module cache, so + // disable the network. This should speed things up, and has + // prevented errors in at least one case, #28518. + tmpCfg.Env = append([]string{"GOPROXY=off"}, cfg.Env...) + + var err error + tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery") + if err != nil { + return err + } + defer os.RemoveAll(tmpCfg.Dir) + + if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil { + return fmt.Errorf("writing go.mod for module cache query: %v", err) + } + + // Run the query, using the import paths calculated from the matches above. + resp, err := driver(&tmpCfg, imports...) + if err != nil { + return fmt.Errorf("querying module cache matches: %v", err) + } + addResponse(resp) + } + + return nil +} + +func getSizes(cfg *Config) (types.Sizes, error) { + return packagesdriver.GetSizesGolist(cfg.Context, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg)) +} + +// roots selects the appropriate paths to walk based on the passed-in configuration, +// particularly the environment and the presence of a go.mod in cfg.Dir's parents. +func roots(cfg *Config) ([]gopathwalk.Root, string, error) { + stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD") + if err != nil { + return nil, "", err + } + + fields := strings.Split(stdout.String(), "\n") + if len(fields) != 4 || len(fields[3]) != 0 { + return nil, "", fmt.Errorf("go env returned unexpected output: %q", stdout.String()) + } + goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2] + var modDir string + if gomod != "" { + modDir = filepath.Dir(gomod) + } + + var roots []gopathwalk.Root + // Always add GOROOT. + roots = append(roots, gopathwalk.Root{ + Path: filepath.Join(goroot, "/src"), + Type: gopathwalk.RootGOROOT, + }) + // If modules are enabled, scan the module dir. + if modDir != "" { + roots = append(roots, gopathwalk.Root{ + Path: modDir, + Type: gopathwalk.RootCurrentModule, + }) + } + // Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode. + for _, p := range gopath { + if modDir != "" { + roots = append(roots, gopathwalk.Root{ + Path: filepath.Join(p, "/pkg/mod"), + Type: gopathwalk.RootModuleCache, + }) + } else { + roots = append(roots, gopathwalk.Root{ + Path: filepath.Join(p, "/src"), + Type: gopathwalk.RootGOPATH, + }) + } + } + + return roots, modDir, nil +} + +// These functions were copied from goimports. See further documentation there. + +// pathMatchesQueries is adapted from pkgIsCandidate. +// TODO: is it reasonable to do Contains here, rather than an exact match on a path component? +func pathMatchesQueries(path string, queries []string) bool { + lastTwo := lastTwoComponents(path) + for _, query := range queries { + if strings.Contains(lastTwo, query) { + return true + } + if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(query) { + lastTwo = lowerASCIIAndRemoveHyphen(lastTwo) + if strings.Contains(lastTwo, query) { + return true + } + } + } + return false +} + +// lastTwoComponents returns at most the last two path components +// of v, using either / or \ as the path separator. +func lastTwoComponents(v string) string { + nslash := 0 + for i := len(v) - 1; i >= 0; i-- { + if v[i] == '/' || v[i] == '\\' { + nslash++ + if nslash == 2 { + return v[i:] + } + } + } + return v +} + +func hasHyphenOrUpperASCII(s string) bool { + for i := 0; i < len(s); i++ { + b := s[i] + if b == '-' || ('A' <= b && b <= 'Z') { + return true + } + } + return false +} + +func lowerASCIIAndRemoveHyphen(s string) (ret string) { + buf := make([]byte, 0, len(s)) + for i := 0; i < len(s); i++ { + b := s[i] + switch { + case b == '-': + continue + case 'A' <= b && b <= 'Z': + buf = append(buf, b+('a'-'A')) + default: + buf = append(buf, b) + } + } + return string(buf) +} + +// Fields must match go list; +// see $GOROOT/src/cmd/go/internal/load/pkg.go. +type jsonPackage struct { + ImportPath string + Dir string + Name string + Export string + GoFiles []string + CompiledGoFiles []string + CFiles []string + CgoFiles []string + CXXFiles []string + MFiles []string + HFiles []string + FFiles []string + SFiles []string + SwigFiles []string + SwigCXXFiles []string + SysoFiles []string + Imports []string + ImportMap map[string]string + Deps []string + TestGoFiles []string + TestImports []string + XTestGoFiles []string + XTestImports []string + ForTest string // q in a "p [q.test]" package, else "" + DepOnly bool + + Error *jsonPackageError +} + +type jsonPackageError struct { + ImportStack []string + Pos string + Err string +} + +func otherFiles(p *jsonPackage) [][]string { + return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} +} + +// golistDriver uses the "go list" command to expand the pattern +// words and return metadata for the specified packages. dir may be +// "" and env may be nil, as per os/exec.Command. +func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driverResponse, error) { + // go list uses the following identifiers in ImportPath and Imports: + // + // "p" -- importable package or main (command) + // "q.test" -- q's test executable + // "p [q.test]" -- variant of p as built for q's test executable + // "q_test [q.test]" -- q's external test package + // + // The packages p that are built differently for a test q.test + // are q itself, plus any helpers used by the external test q_test, + // typically including "testing" and all its dependencies. + + // Run "go list" for complete + // information on the specified packages. + buf, err := invokeGo(cfg, golistargs(cfg, words)...) + if err != nil { + return nil, err + } + seen := make(map[string]*jsonPackage) + // Decode the JSON and convert it to Package form. + var response driverResponse + for dec := json.NewDecoder(buf); dec.More(); { + p := new(jsonPackage) + if err := dec.Decode(p); err != nil { + return nil, fmt.Errorf("JSON decoding failed: %v", err) + } + + if p.ImportPath == "" { + // The documentation for go list says that “[e]rroneous packages will have + // a non-empty ImportPath”. If for some reason it comes back empty, we + // prefer to error out rather than silently discarding data or handing + // back a package without any way to refer to it. + if p.Error != nil { + return nil, Error{ + Pos: p.Error.Pos, + Msg: p.Error.Err, + } + } + return nil, fmt.Errorf("package missing import path: %+v", p) + } + + // Work around https://golang.org/issue/33157: + // go list -e, when given an absolute path, will find the package contained at + // that directory. But when no package exists there, it will return a fake package + // with an error and the ImportPath set to the absolute path provided to go list. + // Try to convert that absolute path to what its package path would be if it's + // contained in a known module or GOPATH entry. This will allow the package to be + // properly "reclaimed" when overlays are processed. + if filepath.IsAbs(p.ImportPath) && p.Error != nil { + pkgPath, ok := getPkgPath(cfg, p.ImportPath, rootsDirs) + if ok { + p.ImportPath = pkgPath + } + } + + if old, found := seen[p.ImportPath]; found { + if !reflect.DeepEqual(p, old) { + return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) + } + // skip the duplicate + continue + } + seen[p.ImportPath] = p + + pkg := &Package{ + Name: p.Name, + ID: p.ImportPath, + GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), + CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), + OtherFiles: absJoin(p.Dir, otherFiles(p)...), + } + + // Work around https://golang.org/issue/28749: + // cmd/go puts assembly, C, and C++ files in CompiledGoFiles. + // Filter out any elements of CompiledGoFiles that are also in OtherFiles. + // We have to keep this workaround in place until go1.12 is a distant memory. + if len(pkg.OtherFiles) > 0 { + other := make(map[string]bool, len(pkg.OtherFiles)) + for _, f := range pkg.OtherFiles { + other[f] = true + } + + out := pkg.CompiledGoFiles[:0] + for _, f := range pkg.CompiledGoFiles { + if other[f] { + continue + } + out = append(out, f) + } + pkg.CompiledGoFiles = out + } + + // Extract the PkgPath from the package's ID. + if i := strings.IndexByte(pkg.ID, ' '); i >= 0 { + pkg.PkgPath = pkg.ID[:i] + } else { + pkg.PkgPath = pkg.ID + } + + if pkg.PkgPath == "unsafe" { + pkg.GoFiles = nil // ignore fake unsafe.go file + } + + // Assume go list emits only absolute paths for Dir. + if p.Dir != "" && !filepath.IsAbs(p.Dir) { + log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir) + } + + if p.Export != "" && !filepath.IsAbs(p.Export) { + pkg.ExportFile = filepath.Join(p.Dir, p.Export) + } else { + pkg.ExportFile = p.Export + } + + // imports + // + // Imports contains the IDs of all imported packages. + // ImportsMap records (path, ID) only where they differ. + ids := make(map[string]bool) + for _, id := range p.Imports { + ids[id] = true + } + pkg.Imports = make(map[string]*Package) + for path, id := range p.ImportMap { + pkg.Imports[path] = &Package{ID: id} // non-identity import + delete(ids, id) + } + for id := range ids { + if id == "C" { + continue + } + + pkg.Imports[id] = &Package{ID: id} // identity import + } + if !p.DepOnly { + response.Roots = append(response.Roots, pkg.ID) + } + + // Work around for pre-go.1.11 versions of go list. + // TODO(matloob): they should be handled by the fallback. + // Can we delete this? + if len(pkg.CompiledGoFiles) == 0 { + pkg.CompiledGoFiles = pkg.GoFiles + } + + if p.Error != nil { + pkg.Errors = append(pkg.Errors, Error{ + Pos: p.Error.Pos, + Msg: strings.TrimSpace(p.Error.Err), // Trim to work around golang.org/issue/32363. + }) + } + + response.Packages = append(response.Packages, pkg) + } + + return &response, nil +} + +// getPkgPath finds the package path of a directory if it's relative to a root directory. +func getPkgPath(cfg *Config, dir string, goInfo func() *goInfo) (string, bool) { + absDir, err := filepath.Abs(dir) + if err != nil { + cfg.Logf("error getting absolute path of %s: %v", dir, err) + return "", false + } + for rdir, rpath := range goInfo().rootDirs { + absRdir, err := filepath.Abs(rdir) + if err != nil { + cfg.Logf("error getting absolute path of %s: %v", rdir, err) + continue + } + // Make sure that the directory is in the module, + // to avoid creating a path relative to another module. + if !strings.HasPrefix(absDir, absRdir) { + cfg.Logf("%s does not have prefix %s", absDir, absRdir) + continue + } + // TODO(matloob): This doesn't properly handle symlinks. + r, err := filepath.Rel(rdir, dir) + if err != nil { + continue + } + if rpath != "" { + // We choose only one root even though the directory even it can belong in multiple modules + // or GOPATH entries. This is okay because we only need to work with absolute dirs when a + // file is missing from disk, for instance when gopls calls go/packages in an overlay. + // Once the file is saved, gopls, or the next invocation of the tool will get the correct + // result straight from golist. + // TODO(matloob): Implement module tiebreaking? + return path.Join(rpath, filepath.ToSlash(r)), true + } + return filepath.ToSlash(r), true + } + return "", false +} + +// absJoin absolutizes and flattens the lists of files. +func absJoin(dir string, fileses ...[]string) (res []string) { + for _, files := range fileses { + for _, file := range files { + if !filepath.IsAbs(file) { + file = filepath.Join(dir, file) + } + res = append(res, file) + } + } + return res +} + +func golistargs(cfg *Config, words []string) []string { + const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo + fullargs := []string{ + "list", "-e", "-json", + fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypesInfo|NeedTypesSizes) != 0), + fmt.Sprintf("-test=%t", cfg.Tests), + fmt.Sprintf("-export=%t", usesExportData(cfg)), + fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0), + // go list doesn't let you pass -test and -find together, + // probably because you'd just get the TestMain. + fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0), + } + fullargs = append(fullargs, cfg.BuildFlags...) + fullargs = append(fullargs, "--") + fullargs = append(fullargs, words...) + return fullargs +} + +// invokeGo returns the stdout of a go command invocation. +func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd := exec.CommandContext(cfg.Context, "go", args...) + // On darwin the cwd gets resolved to the real path, which breaks anything that + // expects the working directory to keep the original path, including the + // go command when dealing with modules. + // The Go stdlib has a special feature where if the cwd and the PWD are the + // same node then it trusts the PWD, so by setting it in the env for the child + // process we fix up all the paths returned by the go command. + cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir) + cmd.Dir = cfg.Dir + cmd.Stdout = stdout + cmd.Stderr = stderr + defer func(start time.Time) { + cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr, stdout) + }(time.Now()) + + if err := cmd.Run(); err != nil { + // Check for 'go' executable not being found. + if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { + return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound) + } + + exitErr, ok := err.(*exec.ExitError) + if !ok { + // Catastrophic error: + // - context cancellation + return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) + } + + // Old go version? + if strings.Contains(stderr.String(), "flag provided but not defined") { + return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)} + } + + // Related to #24854 + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "unexpected directory layout") { + return nil, fmt.Errorf("%s", stderr.String()) + } + + // Is there an error running the C compiler in cgo? This will be reported in the "Error" field + // and should be suppressed by go list -e. + // + // This condition is not perfect yet because the error message can include other error messages than runtime/cgo. + isPkgPathRune := func(r rune) bool { + // From https://golang.org/ref/spec#Import_declarations: + // Implementation restriction: A compiler may restrict ImportPaths to non-empty strings + // using only characters belonging to Unicode's L, M, N, P, and S general categories + // (the Graphic characters without spaces) and may also exclude the + // characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD. + return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) && + !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) + } + if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") { + if strings.HasPrefix(strings.TrimLeftFunc(stderr.String()[len("# "):], isPkgPathRune), "\n") { + return stdout, nil + } + } + + // This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show + // the error in the Err section of stdout in case -e option is provided. + // This fix is provided for backwards compatibility. + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must be .go files") { + output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + + // Similar to the previous error, but currently lacks a fix in Go. + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must all be in one directory") { + output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + + // Backwards compatibility for Go 1.11 because 1.12 and 1.13 put the directory in the ImportPath. + // If the package doesn't exist, put the absolute path of the directory into the error message, + // as Go 1.13 list does. + const noSuchDirectory = "no such directory" + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), noSuchDirectory) { + errstr := stderr.String() + abspath := strings.TrimSpace(errstr[strings.Index(errstr, noSuchDirectory)+len(noSuchDirectory):]) + output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + abspath, strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + + // Workaround for #29280: go list -e has incorrect behavior when an ad-hoc package doesn't exist. + // Note that the error message we look for in this case is different that the one looked for above. + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no such file or directory") { + output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + + // Workaround for #34273. go list -e with GO111MODULE=on has incorrect behavior when listing a + // directory outside any module. + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside available modules") { + output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + // TODO(matloob): command-line-arguments isn't correct here. + "command-line-arguments", strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + + // Another variation of the previous error + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside module root") { + output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + // TODO(matloob): command-line-arguments isn't correct here. + "command-line-arguments", strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + + // Workaround for an instance of golang.org/issue/26755: go list -e will return a non-zero exit + // status if there's a dependency on a package that doesn't exist. But it should return + // a zero exit status and set an error on that package. + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { + // Don't clobber stdout if `go list` actually returned something. + if len(stdout.String()) > 0 { + return stdout, nil + } + // try to extract package name from string + stderrStr := stderr.String() + var importPath string + colon := strings.Index(stderrStr, ":") + if colon > 0 && strings.HasPrefix(stderrStr, "go build ") { + importPath = stderrStr[len("go build "):colon] + } + output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + importPath, strings.Trim(stderrStr, "\n")) + return bytes.NewBufferString(output), nil + } + + // Export mode entails a build. + // If that build fails, errors appear on stderr + // (despite the -e flag) and the Export field is blank. + // Do not fail in that case. + // The same is true if an ad-hoc package given to go list doesn't exist. + // TODO(matloob): Remove these once we can depend on go list to exit with a zero status with -e even when + // packages don't exist or a build fails. + if !usesExportData(cfg) && !containsGoFile(args) { + return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) + } + } + + // As of writing, go list -export prints some non-fatal compilation + // errors to stderr, even with -e set. We would prefer that it put + // them in the Package.Error JSON (see https://golang.org/issue/26319). + // In the meantime, there's nowhere good to put them, but they can + // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS + // is set. + if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { + fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr) + } + return stdout, nil +} + +func containsGoFile(s []string) bool { + for _, f := range s { + if strings.HasSuffix(f, ".go") { + return true + } + } + return false +} + +func cmdDebugStr(cmd *exec.Cmd, args ...string) string { + env := make(map[string]string) + for _, kv := range cmd.Env { + split := strings.Split(kv, "=") + k, v := split[0], split[1] + env[k] = v + } + var quotedArgs []string + for _, arg := range args { + quotedArgs = append(quotedArgs, strconv.Quote(arg)) + } + + return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %s", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], strings.Join(quotedArgs, " ")) +} diff --git a/vendor/golang.org/x/tools/go/packages/golist_overlay.go b/vendor/golang.org/x/tools/go/packages/golist_overlay.go new file mode 100644 index 00000000..a7de6229 --- /dev/null +++ b/vendor/golang.org/x/tools/go/packages/golist_overlay.go @@ -0,0 +1,293 @@ +package packages + +import ( + "bytes" + "encoding/json" + "fmt" + "go/parser" + "go/token" + "path/filepath" + "strconv" + "strings" +) + +// processGolistOverlay provides rudimentary support for adding +// files that don't exist on disk to an overlay. The results can be +// sometimes incorrect. +// TODO(matloob): Handle unsupported cases, including the following: +// - determining the correct package to add given a new import path +func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func() *goInfo) (modifiedPkgs, needPkgs []string, err error) { + havePkgs := make(map[string]string) // importPath -> non-test package ID + needPkgsSet := make(map[string]bool) + modifiedPkgsSet := make(map[string]bool) + + for _, pkg := range response.dr.Packages { + // This is an approximation of import path to id. This can be + // wrong for tests, vendored packages, and a number of other cases. + havePkgs[pkg.PkgPath] = pkg.ID + } + + // If no new imports are added, it is safe to avoid loading any needPkgs. + // Otherwise, it's hard to tell which package is actually being loaded + // (due to vendoring) and whether any modified package will show up + // in the transitive set of dependencies (because new imports are added, + // potentially modifying the transitive set of dependencies). + var overlayAddsImports bool + + for opath, contents := range cfg.Overlay { + base := filepath.Base(opath) + dir := filepath.Dir(opath) + var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant + var testVariantOf *Package // if opath is a test file, this is the package it is testing + var fileExists bool + isTestFile := strings.HasSuffix(opath, "_test.go") + pkgName, ok := extractPackageName(opath, contents) + if !ok { + // Don't bother adding a file that doesn't even have a parsable package statement + // to the overlay. + continue + } + nextPackage: + for _, p := range response.dr.Packages { + if pkgName != p.Name && p.ID != "command-line-arguments" { + continue + } + for _, f := range p.GoFiles { + if !sameFile(filepath.Dir(f), dir) { + continue + } + // Make sure to capture information on the package's test variant, if needed. + if isTestFile && !hasTestFiles(p) { + // TODO(matloob): Are there packages other than the 'production' variant + // of a package that this can match? This shouldn't match the test main package + // because the file is generated in another directory. + testVariantOf = p + continue nextPackage + } + if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath { + // If we've already seen the test variant, + // make sure to label which package it is a test variant of. + if hasTestFiles(pkg) { + testVariantOf = p + continue nextPackage + } + // If we have already seen the package of which this is a test variant. + if hasTestFiles(p) { + testVariantOf = pkg + } + } + pkg = p + if filepath.Base(f) == base { + fileExists = true + } + } + } + // The overlay could have included an entirely new package. + if pkg == nil { + // Try to find the module or gopath dir the file is contained in. + // Then for modules, add the module opath to the beginning. + pkgPath, ok := getPkgPath(cfg, dir, rootDirs) + if !ok { + break + } + isXTest := strings.HasSuffix(pkgName, "_test") + if isXTest { + pkgPath += "_test" + } + id := pkgPath + if isTestFile && !isXTest { + id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath) + } + // Try to reclaim a package with the same id if it exists in the response. + for _, p := range response.dr.Packages { + if reclaimPackage(p, id, opath, contents) { + pkg = p + break + } + } + // Otherwise, create a new package + if pkg == nil { + pkg = &Package{PkgPath: pkgPath, ID: id, Name: pkgName, Imports: make(map[string]*Package)} + response.addPackage(pkg) + havePkgs[pkg.PkgPath] = id + // Add the production package's sources for a test variant. + if isTestFile && !isXTest && testVariantOf != nil { + pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) + pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) + } + } + } + if !fileExists { + pkg.GoFiles = append(pkg.GoFiles, opath) + // TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior + // if the file will be ignored due to its build tags. + pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath) + modifiedPkgsSet[pkg.ID] = true + } + imports, err := extractImports(opath, contents) + if err != nil { + // Let the parser or type checker report errors later. + continue + } + for _, imp := range imports { + _, found := pkg.Imports[imp] + if !found { + overlayAddsImports = true + // TODO(matloob): Handle cases when the following block isn't correct. + // These include imports of vendored packages, etc. + id, ok := havePkgs[imp] + if !ok { + id = imp + } + pkg.Imports[imp] = &Package{ID: id} + // Add dependencies to the non-test variant version of this package as wel. + if testVariantOf != nil { + testVariantOf.Imports[imp] = &Package{ID: id} + } + } + } + continue + } + + // toPkgPath tries to guess the package path given the id. + // This isn't always correct -- it's certainly wrong for + // vendored packages' paths. + toPkgPath := func(id string) string { + // TODO(matloob): Handle vendor paths. + i := strings.IndexByte(id, ' ') + if i >= 0 { + return id[:i] + } + return id + } + + // Do another pass now that new packages have been created to determine the + // set of missing packages. + for _, pkg := range response.dr.Packages { + for _, imp := range pkg.Imports { + pkgPath := toPkgPath(imp.ID) + if _, ok := havePkgs[pkgPath]; !ok { + needPkgsSet[pkgPath] = true + } + } + } + + if overlayAddsImports { + needPkgs = make([]string, 0, len(needPkgsSet)) + for pkg := range needPkgsSet { + needPkgs = append(needPkgs, pkg) + } + } + modifiedPkgs = make([]string, 0, len(modifiedPkgsSet)) + for pkg := range modifiedPkgsSet { + modifiedPkgs = append(modifiedPkgs, pkg) + } + return modifiedPkgs, needPkgs, err +} + +func hasTestFiles(p *Package) bool { + for _, f := range p.GoFiles { + if strings.HasSuffix(f, "_test.go") { + return true + } + } + return false +} + +// determineRootDirs returns a mapping from directories code can be contained in to the +// corresponding import path prefixes of those directories. +// Its result is used to try to determine the import path for a package containing +// an overlay file. +func determineRootDirs(cfg *Config) map[string]string { + // Assume modules first: + out, err := invokeGo(cfg, "list", "-m", "-json", "all") + if err != nil { + return determineRootDirsGOPATH(cfg) + } + m := map[string]string{} + type jsonMod struct{ Path, Dir string } + for dec := json.NewDecoder(out); dec.More(); { + mod := new(jsonMod) + if err := dec.Decode(mod); err != nil { + return m // Give up and return an empty map. Package won't be found for overlay. + } + if mod.Dir != "" && mod.Path != "" { + // This is a valid module; add it to the map. + m[mod.Dir] = mod.Path + } + } + return m +} + +func determineRootDirsGOPATH(cfg *Config) map[string]string { + m := map[string]string{} + out, err := invokeGo(cfg, "env", "GOPATH") + if err != nil { + // Could not determine root dir mapping. Everything is best-effort, so just return an empty map. + // When we try to find the import path for a directory, there will be no root-dir match and + // we'll give up. + return m + } + for _, p := range filepath.SplitList(string(bytes.TrimSpace(out.Bytes()))) { + m[filepath.Join(p, "src")] = "" + } + return m +} + +func extractImports(filename string, contents []byte) ([]string, error) { + f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset? + if err != nil { + return nil, err + } + var res []string + for _, imp := range f.Imports { + quotedPath := imp.Path.Value + path, err := strconv.Unquote(quotedPath) + if err != nil { + return nil, err + } + res = append(res, path) + } + return res, nil +} + +// reclaimPackage attempts to reuse a package that failed to load in an overlay. +// +// If the package has errors and has no Name, GoFiles, or Imports, +// then it's possible that it doesn't yet exist on disk. +func reclaimPackage(pkg *Package, id string, filename string, contents []byte) bool { + // TODO(rstambler): Check the message of the actual error? + // It differs between $GOPATH and module mode. + if pkg.ID != id { + return false + } + if len(pkg.Errors) != 1 { + return false + } + if pkg.Name != "" || pkg.ExportFile != "" { + return false + } + if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 { + return false + } + if len(pkg.Imports) > 0 { + return false + } + pkgName, ok := extractPackageName(filename, contents) + if !ok { + return false + } + pkg.Name = pkgName + pkg.Errors = nil + return true +} + +func extractPackageName(filename string, contents []byte) (string, bool) { + // TODO(rstambler): Check the message of the actual error? + // It differs between $GOPATH and module mode. + f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset? + if err != nil { + return "", false + } + return f.Name.Name, true +} diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go new file mode 100644 index 00000000..b29c9136 --- /dev/null +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -0,0 +1,1113 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packages + +// See doc.go for package documentation and implementation notes. + +import ( + "context" + "encoding/json" + "fmt" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "go/types" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + "sync" + + "golang.org/x/tools/go/gcexportdata" +) + +// A LoadMode controls the amount of detail to return when loading. +// The bits below can be combined to specify which fields should be +// filled in the result packages. +// The zero value is a special case, equivalent to combining +// the NeedName, NeedFiles, and NeedCompiledGoFiles bits. +// ID and Errors (if present) will always be filled. +// Load may return more information than requested. +type LoadMode int + +const ( + // NeedName adds Name and PkgPath. + NeedName LoadMode = 1 << iota + + // NeedFiles adds GoFiles and OtherFiles. + NeedFiles + + // NeedCompiledGoFiles adds CompiledGoFiles. + NeedCompiledGoFiles + + // NeedImports adds Imports. If NeedDeps is not set, the Imports field will contain + // "placeholder" Packages with only the ID set. + NeedImports + + // NeedDeps adds the fields requested by the LoadMode in the packages in Imports. + NeedDeps + + // NeedExportsFile adds ExportsFile. + NeedExportsFile + + // NeedTypes adds Types, Fset, and IllTyped. + NeedTypes + + // NeedSyntax adds Syntax. + NeedSyntax + + // NeedTypesInfo adds TypesInfo. + NeedTypesInfo + + // NeedTypesSizes adds TypesSizes. + NeedTypesSizes +) + +const ( + // Deprecated: LoadFiles exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. + LoadFiles = NeedName | NeedFiles | NeedCompiledGoFiles + + // Deprecated: LoadImports exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. + LoadImports = LoadFiles | NeedImports + + // Deprecated: LoadTypes exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. + LoadTypes = LoadImports | NeedTypes | NeedTypesSizes + + // Deprecated: LoadSyntax exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. + LoadSyntax = LoadTypes | NeedSyntax | NeedTypesInfo + + // Deprecated: LoadAllSyntax exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. + LoadAllSyntax = LoadSyntax | NeedDeps +) + +// A Config specifies details about how packages should be loaded. +// The zero value is a valid configuration. +// Calls to Load do not modify this struct. +type Config struct { + // Mode controls the level of information returned for each package. + Mode LoadMode + + // Context specifies the context for the load operation. + // If the context is cancelled, the loader may stop early + // and return an ErrCancelled error. + // If Context is nil, the load cannot be cancelled. + Context context.Context + + // Logf is the logger for the config. + // If the user provides a logger, debug logging is enabled. + // If the GOPACKAGESDEBUG environment variable is set to true, + // but the logger is nil, default to log.Printf. + Logf func(format string, args ...interface{}) + + // Dir is the directory in which to run the build system's query tool + // that provides information about the packages. + // If Dir is empty, the tool is run in the current directory. + Dir string + + // Env is the environment to use when invoking the build system's query tool. + // If Env is nil, the current environment is used. + // As in os/exec's Cmd, only the last value in the slice for + // each environment key is used. To specify the setting of only + // a few variables, append to the current environment, as in: + // + // opt.Env = append(os.Environ(), "GOOS=plan9", "GOARCH=386") + // + Env []string + + // BuildFlags is a list of command-line flags to be passed through to + // the build system's query tool. + BuildFlags []string + + // Fset provides source position information for syntax trees and types. + // If Fset is nil, Load will use a new fileset, but preserve Fset's value. + Fset *token.FileSet + + // ParseFile is called to read and parse each file + // when preparing a package's type-checked syntax tree. + // It must be safe to call ParseFile simultaneously from multiple goroutines. + // If ParseFile is nil, the loader will uses parser.ParseFile. + // + // ParseFile should parse the source from src and use filename only for + // recording position information. + // + // An application may supply a custom implementation of ParseFile + // to change the effective file contents or the behavior of the parser, + // or to modify the syntax tree. For example, selectively eliminating + // unwanted function bodies can significantly accelerate type checking. + ParseFile func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) + + // If Tests is set, the loader includes not just the packages + // matching a particular pattern but also any related test packages, + // including test-only variants of the package and the test executable. + // + // For example, when using the go command, loading "fmt" with Tests=true + // returns four packages, with IDs "fmt" (the standard package), + // "fmt [fmt.test]" (the package as compiled for the test), + // "fmt_test" (the test functions from source files in package fmt_test), + // and "fmt.test" (the test binary). + // + // In build systems with explicit names for tests, + // setting Tests may have no effect. + Tests bool + + // Overlay provides a mapping of absolute file paths to file contents. + // If the file with the given path already exists, the parser will use the + // alternative file contents provided by the map. + // + // Overlays provide incomplete support for when a given file doesn't + // already exist on disk. See the package doc above for more details. + Overlay map[string][]byte +} + +// driver is the type for functions that query the build system for the +// packages named by the patterns. +type driver func(cfg *Config, patterns ...string) (*driverResponse, error) + +// driverResponse contains the results for a driver query. +type driverResponse struct { + // Sizes, if not nil, is the types.Sizes to use when type checking. + Sizes *types.StdSizes + + // Roots is the set of package IDs that make up the root packages. + // We have to encode this separately because when we encode a single package + // we cannot know if it is one of the roots as that requires knowledge of the + // graph it is part of. + Roots []string `json:",omitempty"` + + // Packages is the full set of packages in the graph. + // The packages are not connected into a graph. + // The Imports if populated will be stubs that only have their ID set. + // Imports will be connected and then type and syntax information added in a + // later pass (see refine). + Packages []*Package +} + +// Load loads and returns the Go packages named by the given patterns. +// +// Config specifies loading options; +// nil behaves the same as an empty Config. +// +// Load returns an error if any of the patterns was invalid +// as defined by the underlying build system. +// It may return an empty list of packages without an error, +// for instance for an empty expansion of a valid wildcard. +// Errors associated with a particular package are recorded in the +// corresponding Package's Errors list, and do not cause Load to +// return an error. Clients may need to handle such errors before +// proceeding with further analysis. The PrintErrors function is +// provided for convenient display of all errors. +func Load(cfg *Config, patterns ...string) ([]*Package, error) { + l := newLoader(cfg) + response, err := defaultDriver(&l.Config, patterns...) + if err != nil { + return nil, err + } + l.sizes = response.Sizes + return l.refine(response.Roots, response.Packages...) +} + +// defaultDriver is a driver that looks for an external driver binary, and if +// it does not find it falls back to the built in go list driver. +func defaultDriver(cfg *Config, patterns ...string) (*driverResponse, error) { + driver := findExternalDriver(cfg) + if driver == nil { + driver = goListDriver + } + return driver(cfg, patterns...) +} + +// A Package describes a loaded Go package. +type Package struct { + // ID is a unique identifier for a package, + // in a syntax provided by the underlying build system. + // + // Because the syntax varies based on the build system, + // clients should treat IDs as opaque and not attempt to + // interpret them. + ID string + + // Name is the package name as it appears in the package source code. + Name string + + // PkgPath is the package path as used by the go/types package. + PkgPath string + + // Errors contains any errors encountered querying the metadata + // of the package, or while parsing or type-checking its files. + Errors []Error + + // GoFiles lists the absolute file paths of the package's Go source files. + GoFiles []string + + // CompiledGoFiles lists the absolute file paths of the package's source + // files that were presented to the compiler. + // This may differ from GoFiles if files are processed before compilation. + CompiledGoFiles []string + + // OtherFiles lists the absolute file paths of the package's non-Go source files, + // including assembly, C, C++, Fortran, Objective-C, SWIG, and so on. + OtherFiles []string + + // ExportFile is the absolute path to a file containing type + // information for the package as provided by the build system. + ExportFile string + + // Imports maps import paths appearing in the package's Go source files + // to corresponding loaded Packages. + Imports map[string]*Package + + // Types provides type information for the package. + // The NeedTypes LoadMode bit sets this field for packages matching the + // patterns; type information for dependencies may be missing or incomplete, + // unless NeedDeps and NeedImports are also set. + Types *types.Package + + // Fset provides position information for Types, TypesInfo, and Syntax. + // It is set only when Types is set. + Fset *token.FileSet + + // IllTyped indicates whether the package or any dependency contains errors. + // It is set only when Types is set. + IllTyped bool + + // Syntax is the package's syntax trees, for the files listed in CompiledGoFiles. + // + // The NeedSyntax LoadMode bit populates this field for packages matching the patterns. + // If NeedDeps and NeedImports are also set, this field will also be populated + // for dependencies. + Syntax []*ast.File + + // TypesInfo provides type information about the package's syntax trees. + // It is set only when Syntax is set. + TypesInfo *types.Info + + // TypesSizes provides the effective size function for types in TypesInfo. + TypesSizes types.Sizes +} + +// An Error describes a problem with a package's metadata, syntax, or types. +type Error struct { + Pos string // "file:line:col" or "file:line" or "" or "-" + Msg string + Kind ErrorKind +} + +// ErrorKind describes the source of the error, allowing the user to +// differentiate between errors generated by the driver, the parser, or the +// type-checker. +type ErrorKind int + +const ( + UnknownError ErrorKind = iota + ListError + ParseError + TypeError +) + +func (err Error) Error() string { + pos := err.Pos + if pos == "" { + pos = "-" // like token.Position{}.String() + } + return pos + ": " + err.Msg +} + +// flatPackage is the JSON form of Package +// It drops all the type and syntax fields, and transforms the Imports +// +// TODO(adonovan): identify this struct with Package, effectively +// publishing the JSON protocol. +type flatPackage struct { + ID string + Name string `json:",omitempty"` + PkgPath string `json:",omitempty"` + Errors []Error `json:",omitempty"` + GoFiles []string `json:",omitempty"` + CompiledGoFiles []string `json:",omitempty"` + OtherFiles []string `json:",omitempty"` + ExportFile string `json:",omitempty"` + Imports map[string]string `json:",omitempty"` +} + +// MarshalJSON returns the Package in its JSON form. +// For the most part, the structure fields are written out unmodified, and +// the type and syntax fields are skipped. +// The imports are written out as just a map of path to package id. +// The errors are written using a custom type that tries to preserve the +// structure of error types we know about. +// +// This method exists to enable support for additional build systems. It is +// not intended for use by clients of the API and we may change the format. +func (p *Package) MarshalJSON() ([]byte, error) { + flat := &flatPackage{ + ID: p.ID, + Name: p.Name, + PkgPath: p.PkgPath, + Errors: p.Errors, + GoFiles: p.GoFiles, + CompiledGoFiles: p.CompiledGoFiles, + OtherFiles: p.OtherFiles, + ExportFile: p.ExportFile, + } + if len(p.Imports) > 0 { + flat.Imports = make(map[string]string, len(p.Imports)) + for path, ipkg := range p.Imports { + flat.Imports[path] = ipkg.ID + } + } + return json.Marshal(flat) +} + +// UnmarshalJSON reads in a Package from its JSON format. +// See MarshalJSON for details about the format accepted. +func (p *Package) UnmarshalJSON(b []byte) error { + flat := &flatPackage{} + if err := json.Unmarshal(b, &flat); err != nil { + return err + } + *p = Package{ + ID: flat.ID, + Name: flat.Name, + PkgPath: flat.PkgPath, + Errors: flat.Errors, + GoFiles: flat.GoFiles, + CompiledGoFiles: flat.CompiledGoFiles, + OtherFiles: flat.OtherFiles, + ExportFile: flat.ExportFile, + } + if len(flat.Imports) > 0 { + p.Imports = make(map[string]*Package, len(flat.Imports)) + for path, id := range flat.Imports { + p.Imports[path] = &Package{ID: id} + } + } + return nil +} + +func (p *Package) String() string { return p.ID } + +// loaderPackage augments Package with state used during the loading phase +type loaderPackage struct { + *Package + importErrors map[string]error // maps each bad import to its error + loadOnce sync.Once + color uint8 // for cycle detection + needsrc bool // load from source (Mode >= LoadTypes) + needtypes bool // type information is either requested or depended on + initial bool // package was matched by a pattern +} + +// loader holds the working state of a single call to load. +type loader struct { + pkgs map[string]*loaderPackage + Config + sizes types.Sizes + parseCache map[string]*parseValue + parseCacheMu sync.Mutex + exportMu sync.Mutex // enforces mutual exclusion of exportdata operations + + // Config.Mode contains the implied mode (see impliedLoadMode). + // Implied mode contains all the fields we need the data for. + // In requestedMode there are the actually requested fields. + // We'll zero them out before returning packages to the user. + // This makes it easier for us to get the conditions where + // we need certain modes right. + requestedMode LoadMode +} + +type parseValue struct { + f *ast.File + err error + ready chan struct{} +} + +func newLoader(cfg *Config) *loader { + ld := &loader{ + parseCache: map[string]*parseValue{}, + } + if cfg != nil { + ld.Config = *cfg + // If the user has provided a logger, use it. + ld.Config.Logf = cfg.Logf + } + if ld.Config.Logf == nil { + // If the GOPACKAGESDEBUG environment variable is set to true, + // but the user has not provided a logger, default to log.Printf. + if debug { + ld.Config.Logf = log.Printf + } else { + ld.Config.Logf = func(format string, args ...interface{}) {} + } + } + if ld.Config.Mode == 0 { + ld.Config.Mode = NeedName | NeedFiles | NeedCompiledGoFiles // Preserve zero behavior of Mode for backwards compatibility. + } + if ld.Config.Env == nil { + ld.Config.Env = os.Environ() + } + if ld.Context == nil { + ld.Context = context.Background() + } + if ld.Dir == "" { + if dir, err := os.Getwd(); err == nil { + ld.Dir = dir + } + } + + // Save the actually requested fields. We'll zero them out before returning packages to the user. + ld.requestedMode = ld.Mode + ld.Mode = impliedLoadMode(ld.Mode) + + if ld.Mode&NeedTypes != 0 { + if ld.Fset == nil { + ld.Fset = token.NewFileSet() + } + + // ParseFile is required even in LoadTypes mode + // because we load source if export data is missing. + if ld.ParseFile == nil { + ld.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) { + const mode = parser.AllErrors | parser.ParseComments + return parser.ParseFile(fset, filename, src, mode) + } + } + } + + return ld +} + +// refine connects the supplied packages into a graph and then adds type and +// and syntax information as requested by the LoadMode. +func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { + rootMap := make(map[string]int, len(roots)) + for i, root := range roots { + rootMap[root] = i + } + ld.pkgs = make(map[string]*loaderPackage) + // first pass, fixup and build the map and roots + var initial = make([]*loaderPackage, len(roots)) + for _, pkg := range list { + rootIndex := -1 + if i, found := rootMap[pkg.ID]; found { + rootIndex = i + } + lpkg := &loaderPackage{ + Package: pkg, + needtypes: (ld.Mode&(NeedTypes|NeedTypesInfo) != 0 && ld.Mode&NeedDeps != 0 && rootIndex < 0) || rootIndex >= 0, + needsrc: (ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && ld.Mode&NeedDeps != 0 && rootIndex < 0) || rootIndex >= 0 || + len(ld.Overlay) > 0 || // Overlays can invalidate export data. TODO(matloob): make this check fine-grained based on dependencies on overlaid files + pkg.ExportFile == "" && pkg.PkgPath != "unsafe", + } + ld.pkgs[lpkg.ID] = lpkg + if rootIndex >= 0 { + initial[rootIndex] = lpkg + lpkg.initial = true + } + } + for i, root := range roots { + if initial[i] == nil { + return nil, fmt.Errorf("root package %v is missing", root) + } + } + + // Materialize the import graph. + + const ( + white = 0 // new + grey = 1 // in progress + black = 2 // complete + ) + + // visit traverses the import graph, depth-first, + // and materializes the graph as Packages.Imports. + // + // Valid imports are saved in the Packages.Import map. + // Invalid imports (cycles and missing nodes) are saved in the importErrors map. + // Thus, even in the presence of both kinds of errors, the Import graph remains a DAG. + // + // visit returns whether the package needs src or has a transitive + // dependency on a package that does. These are the only packages + // for which we load source code. + var stack []*loaderPackage + var visit func(lpkg *loaderPackage) bool + var srcPkgs []*loaderPackage + visit = func(lpkg *loaderPackage) bool { + switch lpkg.color { + case black: + return lpkg.needsrc + case grey: + panic("internal error: grey node") + } + lpkg.color = grey + stack = append(stack, lpkg) // push + stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports + // If NeedImports isn't set, the imports fields will all be zeroed out. + if ld.Mode&NeedImports != 0 { + lpkg.Imports = make(map[string]*Package, len(stubs)) + for importPath, ipkg := range stubs { + var importErr error + imp := ld.pkgs[ipkg.ID] + if imp == nil { + // (includes package "C" when DisableCgo) + importErr = fmt.Errorf("missing package: %q", ipkg.ID) + } else if imp.color == grey { + importErr = fmt.Errorf("import cycle: %s", stack) + } + if importErr != nil { + if lpkg.importErrors == nil { + lpkg.importErrors = make(map[string]error) + } + lpkg.importErrors[importPath] = importErr + continue + } + + if visit(imp) { + lpkg.needsrc = true + } + lpkg.Imports[importPath] = imp.Package + } + } + if lpkg.needsrc { + srcPkgs = append(srcPkgs, lpkg) + } + if ld.Mode&NeedTypesSizes != 0 { + lpkg.TypesSizes = ld.sizes + } + stack = stack[:len(stack)-1] // pop + lpkg.color = black + + return lpkg.needsrc + } + + if ld.Mode&NeedImports == 0 { + // We do this to drop the stub import packages that we are not even going to try to resolve. + for _, lpkg := range initial { + lpkg.Imports = nil + } + } else { + // For each initial package, create its import DAG. + for _, lpkg := range initial { + visit(lpkg) + } + } + if ld.Mode&NeedImports != 0 && ld.Mode&NeedTypes != 0 { + for _, lpkg := range srcPkgs { + // Complete type information is required for the + // immediate dependencies of each source package. + for _, ipkg := range lpkg.Imports { + imp := ld.pkgs[ipkg.ID] + imp.needtypes = true + } + } + } + // Load type data if needed, starting at + // the initial packages (roots of the import DAG). + if ld.Mode&NeedTypes != 0 { + var wg sync.WaitGroup + for _, lpkg := range initial { + wg.Add(1) + go func(lpkg *loaderPackage) { + ld.loadRecursive(lpkg) + wg.Done() + }(lpkg) + } + wg.Wait() + } + + result := make([]*Package, len(initial)) + for i, lpkg := range initial { + result[i] = lpkg.Package + } + for i := range ld.pkgs { + // Clear all unrequested fields, for extra de-Hyrum-ization. + if ld.requestedMode&NeedName == 0 { + ld.pkgs[i].Name = "" + ld.pkgs[i].PkgPath = "" + } + if ld.requestedMode&NeedFiles == 0 { + ld.pkgs[i].GoFiles = nil + ld.pkgs[i].OtherFiles = nil + } + if ld.requestedMode&NeedCompiledGoFiles == 0 { + ld.pkgs[i].CompiledGoFiles = nil + } + if ld.requestedMode&NeedImports == 0 { + ld.pkgs[i].Imports = nil + } + if ld.requestedMode&NeedExportsFile == 0 { + ld.pkgs[i].ExportFile = "" + } + if ld.requestedMode&NeedTypes == 0 { + ld.pkgs[i].Types = nil + ld.pkgs[i].Fset = nil + ld.pkgs[i].IllTyped = false + } + if ld.requestedMode&NeedSyntax == 0 { + ld.pkgs[i].Syntax = nil + } + if ld.requestedMode&NeedTypesInfo == 0 { + ld.pkgs[i].TypesInfo = nil + } + if ld.requestedMode&NeedTypesSizes == 0 { + ld.pkgs[i].TypesSizes = nil + } + } + + return result, nil +} + +// loadRecursive loads the specified package and its dependencies, +// recursively, in parallel, in topological order. +// It is atomic and idempotent. +// Precondition: ld.Mode&NeedTypes. +func (ld *loader) loadRecursive(lpkg *loaderPackage) { + lpkg.loadOnce.Do(func() { + // Load the direct dependencies, in parallel. + var wg sync.WaitGroup + for _, ipkg := range lpkg.Imports { + imp := ld.pkgs[ipkg.ID] + wg.Add(1) + go func(imp *loaderPackage) { + ld.loadRecursive(imp) + wg.Done() + }(imp) + } + wg.Wait() + ld.loadPackage(lpkg) + }) +} + +// loadPackage loads the specified package. +// It must be called only once per Package, +// after immediate dependencies are loaded. +// Precondition: ld.Mode & NeedTypes. +func (ld *loader) loadPackage(lpkg *loaderPackage) { + if lpkg.PkgPath == "unsafe" { + // Fill in the blanks to avoid surprises. + lpkg.Types = types.Unsafe + lpkg.Fset = ld.Fset + lpkg.Syntax = []*ast.File{} + lpkg.TypesInfo = new(types.Info) + lpkg.TypesSizes = ld.sizes + return + } + + // Call NewPackage directly with explicit name. + // This avoids skew between golist and go/types when the files' + // package declarations are inconsistent. + lpkg.Types = types.NewPackage(lpkg.PkgPath, lpkg.Name) + lpkg.Fset = ld.Fset + + // Subtle: we populate all Types fields with an empty Package + // before loading export data so that export data processing + // never has to create a types.Package for an indirect dependency, + // which would then require that such created packages be explicitly + // inserted back into the Import graph as a final step after export data loading. + // The Diamond test exercises this case. + if !lpkg.needtypes { + return + } + if !lpkg.needsrc { + ld.loadFromExportData(lpkg) + return // not a source package, don't get syntax trees + } + + appendError := func(err error) { + // Convert various error types into the one true Error. + var errs []Error + switch err := err.(type) { + case Error: + // from driver + errs = append(errs, err) + + case *os.PathError: + // from parser + errs = append(errs, Error{ + Pos: err.Path + ":1", + Msg: err.Err.Error(), + Kind: ParseError, + }) + + case scanner.ErrorList: + // from parser + for _, err := range err { + errs = append(errs, Error{ + Pos: err.Pos.String(), + Msg: err.Msg, + Kind: ParseError, + }) + } + + case types.Error: + // from type checker + errs = append(errs, Error{ + Pos: err.Fset.Position(err.Pos).String(), + Msg: err.Msg, + Kind: TypeError, + }) + + default: + // unexpected impoverished error from parser? + errs = append(errs, Error{ + Pos: "-", + Msg: err.Error(), + Kind: UnknownError, + }) + + // If you see this error message, please file a bug. + log.Printf("internal error: error %q (%T) without position", err, err) + } + + lpkg.Errors = append(lpkg.Errors, errs...) + } + + if len(lpkg.CompiledGoFiles) == 0 && lpkg.ExportFile != "" { + // The config requested loading sources and types, but sources are missing. + // Add an error to the package and fall back to loading from export data. + appendError(Error{"-", fmt.Sprintf("sources missing for package %s", lpkg.ID), ParseError}) + ld.loadFromExportData(lpkg) + return // can't get syntax trees for this package + } + + files, errs := ld.parseFiles(lpkg.CompiledGoFiles) + for _, err := range errs { + appendError(err) + } + + lpkg.Syntax = files + + lpkg.TypesInfo = &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + } + lpkg.TypesSizes = ld.sizes + + importer := importerFunc(func(path string) (*types.Package, error) { + if path == "unsafe" { + return types.Unsafe, nil + } + + // The imports map is keyed by import path. + ipkg := lpkg.Imports[path] + if ipkg == nil { + if err := lpkg.importErrors[path]; err != nil { + return nil, err + } + // There was skew between the metadata and the + // import declarations, likely due to an edit + // race, or because the ParseFile feature was + // used to supply alternative file contents. + return nil, fmt.Errorf("no metadata for %s", path) + } + + if ipkg.Types != nil && ipkg.Types.Complete() { + return ipkg.Types, nil + } + log.Fatalf("internal error: package %q without types was imported from %q", path, lpkg) + panic("unreachable") + }) + + // type-check + tc := &types.Config{ + Importer: importer, + + // Type-check bodies of functions only in non-initial packages. + // Example: for import graph A->B->C and initial packages {A,C}, + // we can ignore function bodies in B. + IgnoreFuncBodies: ld.Mode&NeedDeps == 0 && !lpkg.initial, + + Error: appendError, + Sizes: ld.sizes, + } + types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) + + lpkg.importErrors = nil // no longer needed + + // If !Cgo, the type-checker uses FakeImportC mode, so + // it doesn't invoke the importer for import "C", + // nor report an error for the import, + // or for any undefined C.f reference. + // We must detect this explicitly and correctly + // mark the package as IllTyped (by reporting an error). + // TODO(adonovan): if these errors are annoying, + // we could just set IllTyped quietly. + if tc.FakeImportC { + outer: + for _, f := range lpkg.Syntax { + for _, imp := range f.Imports { + if imp.Path.Value == `"C"` { + err := types.Error{Fset: ld.Fset, Pos: imp.Pos(), Msg: `import "C" ignored`} + appendError(err) + break outer + } + } + } + } + + // Record accumulated errors. + illTyped := len(lpkg.Errors) > 0 + if !illTyped { + for _, imp := range lpkg.Imports { + if imp.IllTyped { + illTyped = true + break + } + } + } + lpkg.IllTyped = illTyped +} + +// An importFunc is an implementation of the single-method +// types.Importer interface based on a function value. +type importerFunc func(path string) (*types.Package, error) + +func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } + +// We use a counting semaphore to limit +// the number of parallel I/O calls per process. +var ioLimit = make(chan bool, 20) + +func (ld *loader) parseFile(filename string) (*ast.File, error) { + ld.parseCacheMu.Lock() + v, ok := ld.parseCache[filename] + if ok { + // cache hit + ld.parseCacheMu.Unlock() + <-v.ready + } else { + // cache miss + v = &parseValue{ready: make(chan struct{})} + ld.parseCache[filename] = v + ld.parseCacheMu.Unlock() + + var src []byte + for f, contents := range ld.Config.Overlay { + if sameFile(f, filename) { + src = contents + } + } + var err error + if src == nil { + ioLimit <- true // wait + src, err = ioutil.ReadFile(filename) + <-ioLimit // signal + } + if err != nil { + v.err = err + } else { + v.f, v.err = ld.ParseFile(ld.Fset, filename, src) + } + + close(v.ready) + } + return v.f, v.err +} + +// parseFiles reads and parses the Go source files and returns the ASTs +// of the ones that could be at least partially parsed, along with a +// list of I/O and parse errors encountered. +// +// Because files are scanned in parallel, the token.Pos +// positions of the resulting ast.Files are not ordered. +// +func (ld *loader) parseFiles(filenames []string) ([]*ast.File, []error) { + var wg sync.WaitGroup + n := len(filenames) + parsed := make([]*ast.File, n) + errors := make([]error, n) + for i, file := range filenames { + if ld.Config.Context.Err() != nil { + parsed[i] = nil + errors[i] = ld.Config.Context.Err() + continue + } + wg.Add(1) + go func(i int, filename string) { + parsed[i], errors[i] = ld.parseFile(filename) + wg.Done() + }(i, file) + } + wg.Wait() + + // Eliminate nils, preserving order. + var o int + for _, f := range parsed { + if f != nil { + parsed[o] = f + o++ + } + } + parsed = parsed[:o] + + o = 0 + for _, err := range errors { + if err != nil { + errors[o] = err + o++ + } + } + errors = errors[:o] + + return parsed, errors +} + +// sameFile returns true if x and y have the same basename and denote +// the same file. +// +func sameFile(x, y string) bool { + if x == y { + // It could be the case that y doesn't exist. + // For instance, it may be an overlay file that + // hasn't been written to disk. To handle that case + // let x == y through. (We added the exact absolute path + // string to the CompiledGoFiles list, so the unwritten + // overlay case implies x==y.) + return true + } + if strings.EqualFold(filepath.Base(x), filepath.Base(y)) { // (optimisation) + if xi, err := os.Stat(x); err == nil { + if yi, err := os.Stat(y); err == nil { + return os.SameFile(xi, yi) + } + } + } + return false +} + +// loadFromExportData returns type information for the specified +// package, loading it from an export data file on the first request. +func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error) { + if lpkg.PkgPath == "" { + log.Fatalf("internal error: Package %s has no PkgPath", lpkg) + } + + // Because gcexportdata.Read has the potential to create or + // modify the types.Package for each node in the transitive + // closure of dependencies of lpkg, all exportdata operations + // must be sequential. (Finer-grained locking would require + // changes to the gcexportdata API.) + // + // The exportMu lock guards the Package.Pkg field and the + // types.Package it points to, for each Package in the graph. + // + // Not all accesses to Package.Pkg need to be protected by exportMu: + // graph ordering ensures that direct dependencies of source + // packages are fully loaded before the importer reads their Pkg field. + ld.exportMu.Lock() + defer ld.exportMu.Unlock() + + if tpkg := lpkg.Types; tpkg != nil && tpkg.Complete() { + return tpkg, nil // cache hit + } + + lpkg.IllTyped = true // fail safe + + if lpkg.ExportFile == "" { + // Errors while building export data will have been printed to stderr. + return nil, fmt.Errorf("no export data file") + } + f, err := os.Open(lpkg.ExportFile) + if err != nil { + return nil, err + } + defer f.Close() + + // Read gc export data. + // + // We don't currently support gccgo export data because all + // underlying workspaces use the gc toolchain. (Even build + // systems that support gccgo don't use it for workspace + // queries.) + r, err := gcexportdata.NewReader(f) + if err != nil { + return nil, fmt.Errorf("reading %s: %v", lpkg.ExportFile, err) + } + + // Build the view. + // + // The gcexportdata machinery has no concept of package ID. + // It identifies packages by their PkgPath, which although not + // globally unique is unique within the scope of one invocation + // of the linker, type-checker, or gcexportdata. + // + // So, we must build a PkgPath-keyed view of the global + // (conceptually ID-keyed) cache of packages and pass it to + // gcexportdata. The view must contain every existing + // package that might possibly be mentioned by the + // current package---its transitive closure. + // + // In loadPackage, we unconditionally create a types.Package for + // each dependency so that export data loading does not + // create new ones. + // + // TODO(adonovan): it would be simpler and more efficient + // if the export data machinery invoked a callback to + // get-or-create a package instead of a map. + // + view := make(map[string]*types.Package) // view seen by gcexportdata + seen := make(map[*loaderPackage]bool) // all visited packages + var visit func(pkgs map[string]*Package) + visit = func(pkgs map[string]*Package) { + for _, p := range pkgs { + lpkg := ld.pkgs[p.ID] + if !seen[lpkg] { + seen[lpkg] = true + view[lpkg.PkgPath] = lpkg.Types + visit(lpkg.Imports) + } + } + } + visit(lpkg.Imports) + + viewLen := len(view) + 1 // adding the self package + // Parse the export data. + // (May modify incomplete packages in view but not create new ones.) + tpkg, err := gcexportdata.Read(r, ld.Fset, view, lpkg.PkgPath) + if err != nil { + return nil, fmt.Errorf("reading %s: %v", lpkg.ExportFile, err) + } + if viewLen != len(view) { + log.Fatalf("Unexpected package creation during export data loading") + } + + lpkg.Types = tpkg + lpkg.IllTyped = false + + return tpkg, nil +} + +// impliedLoadMode returns loadMode with its dependencies. +func impliedLoadMode(loadMode LoadMode) LoadMode { + if loadMode&NeedTypesInfo != 0 && loadMode&NeedImports == 0 { + // If NeedTypesInfo, go/packages needs to do typechecking itself so it can + // associate type info with the AST. To do so, we need the export data + // for dependencies, which means we need to ask for the direct dependencies. + // NeedImports is used to ask for the direct dependencies. + loadMode |= NeedImports + } + + if loadMode&NeedDeps != 0 && loadMode&NeedImports == 0 { + // With NeedDeps we need to load at least direct dependencies. + // NeedImports is used to ask for the direct dependencies. + loadMode |= NeedImports + } + + return loadMode +} + +func usesExportData(cfg *Config) bool { + return cfg.Mode&NeedExportsFile != 0 || cfg.Mode&NeedTypes != 0 && cfg.Mode&NeedDeps == 0 +} diff --git a/vendor/golang.org/x/tools/go/packages/visit.go b/vendor/golang.org/x/tools/go/packages/visit.go new file mode 100644 index 00000000..b13cb081 --- /dev/null +++ b/vendor/golang.org/x/tools/go/packages/visit.go @@ -0,0 +1,55 @@ +package packages + +import ( + "fmt" + "os" + "sort" +) + +// Visit visits all the packages in the import graph whose roots are +// pkgs, calling the optional pre function the first time each package +// is encountered (preorder), and the optional post function after a +// package's dependencies have been visited (postorder). +// The boolean result of pre(pkg) determines whether +// the imports of package pkg are visited. +func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) { + seen := make(map[*Package]bool) + var visit func(*Package) + visit = func(pkg *Package) { + if !seen[pkg] { + seen[pkg] = true + + if pre == nil || pre(pkg) { + paths := make([]string, 0, len(pkg.Imports)) + for path := range pkg.Imports { + paths = append(paths, path) + } + sort.Strings(paths) // Imports is a map, this makes visit stable + for _, path := range paths { + visit(pkg.Imports[path]) + } + } + + if post != nil { + post(pkg) + } + } + } + for _, pkg := range pkgs { + visit(pkg) + } +} + +// PrintErrors prints to os.Stderr the accumulated errors of all +// packages in the import graph rooted at pkgs, dependencies first. +// PrintErrors returns the number of errors printed. +func PrintErrors(pkgs []*Package) int { + var n int + Visit(pkgs, nil, func(pkg *Package) { + for _, err := range pkg.Errors { + fmt.Fprintln(os.Stderr, err) + n++ + } + }) + return n +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go new file mode 100644 index 00000000..7219c8e9 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go @@ -0,0 +1,196 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package fastwalk provides a faster version of filepath.Walk for file system +// scanning tools. +package fastwalk + +import ( + "errors" + "os" + "path/filepath" + "runtime" + "sync" +) + +// TraverseLink is used as a return value from WalkFuncs to indicate that the +// symlink named in the call may be traversed. +var TraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory") + +// SkipFiles is a used as a return value from WalkFuncs to indicate that the +// callback should not be called for any other files in the current directory. +// Child directories will still be traversed. +var SkipFiles = errors.New("fastwalk: skip remaining files in directory") + +// Walk is a faster implementation of filepath.Walk. +// +// filepath.Walk's design necessarily calls os.Lstat on each file, +// even if the caller needs less info. +// Many tools need only the type of each file. +// On some platforms, this information is provided directly by the readdir +// system call, avoiding the need to stat each file individually. +// fastwalk_unix.go contains a fork of the syscall routines. +// +// See golang.org/issue/16399 +// +// Walk walks the file tree rooted at root, calling walkFn for +// each file or directory in the tree, including root. +// +// If fastWalk returns filepath.SkipDir, the directory is skipped. +// +// Unlike filepath.Walk: +// * file stat calls must be done by the user. +// The only provided metadata is the file type, which does not include +// any permission bits. +// * multiple goroutines stat the filesystem concurrently. The provided +// walkFn must be safe for concurrent use. +// * fastWalk can follow symlinks if walkFn returns the TraverseLink +// sentinel error. It is the walkFn's responsibility to prevent +// fastWalk from going into symlink cycles. +func Walk(root string, walkFn func(path string, typ os.FileMode) error) error { + // TODO(bradfitz): make numWorkers configurable? We used a + // minimum of 4 to give the kernel more info about multiple + // things we want, in hopes its I/O scheduling can take + // advantage of that. Hopefully most are in cache. Maybe 4 is + // even too low of a minimum. Profile more. + numWorkers := 4 + if n := runtime.NumCPU(); n > numWorkers { + numWorkers = n + } + + // Make sure to wait for all workers to finish, otherwise + // walkFn could still be called after returning. This Wait call + // runs after close(e.donec) below. + var wg sync.WaitGroup + defer wg.Wait() + + w := &walker{ + fn: walkFn, + enqueuec: make(chan walkItem, numWorkers), // buffered for performance + workc: make(chan walkItem, numWorkers), // buffered for performance + donec: make(chan struct{}), + + // buffered for correctness & not leaking goroutines: + resc: make(chan error, numWorkers), + } + defer close(w.donec) + + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go w.doWork(&wg) + } + todo := []walkItem{{dir: root}} + out := 0 + for { + workc := w.workc + var workItem walkItem + if len(todo) == 0 { + workc = nil + } else { + workItem = todo[len(todo)-1] + } + select { + case workc <- workItem: + todo = todo[:len(todo)-1] + out++ + case it := <-w.enqueuec: + todo = append(todo, it) + case err := <-w.resc: + out-- + if err != nil { + return err + } + if out == 0 && len(todo) == 0 { + // It's safe to quit here, as long as the buffered + // enqueue channel isn't also readable, which might + // happen if the worker sends both another unit of + // work and its result before the other select was + // scheduled and both w.resc and w.enqueuec were + // readable. + select { + case it := <-w.enqueuec: + todo = append(todo, it) + default: + return nil + } + } + } + } +} + +// doWork reads directories as instructed (via workc) and runs the +// user's callback function. +func (w *walker) doWork(wg *sync.WaitGroup) { + defer wg.Done() + for { + select { + case <-w.donec: + return + case it := <-w.workc: + select { + case <-w.donec: + return + case w.resc <- w.walk(it.dir, !it.callbackDone): + } + } + } +} + +type walker struct { + fn func(path string, typ os.FileMode) error + + donec chan struct{} // closed on fastWalk's return + workc chan walkItem // to workers + enqueuec chan walkItem // from workers + resc chan error // from workers +} + +type walkItem struct { + dir string + callbackDone bool // callback already called; don't do it again +} + +func (w *walker) enqueue(it walkItem) { + select { + case w.enqueuec <- it: + case <-w.donec: + } +} + +func (w *walker) onDirEnt(dirName, baseName string, typ os.FileMode) error { + joined := dirName + string(os.PathSeparator) + baseName + if typ == os.ModeDir { + w.enqueue(walkItem{dir: joined}) + return nil + } + + err := w.fn(joined, typ) + if typ == os.ModeSymlink { + if err == TraverseLink { + // Set callbackDone so we don't call it twice for both the + // symlink-as-symlink and the symlink-as-directory later: + w.enqueue(walkItem{dir: joined, callbackDone: true}) + return nil + } + if err == filepath.SkipDir { + // Permit SkipDir on symlinks too. + return nil + } + } + return err +} + +func (w *walker) walk(root string, runUserCallback bool) error { + if runUserCallback { + err := w.fn(root, os.ModeDir) + if err == filepath.SkipDir { + return nil + } + if err != nil { + return err + } + } + + return readDir(root, w.onDirEnt) +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go new file mode 100644 index 00000000..ccffec5a --- /dev/null +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go @@ -0,0 +1,13 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build freebsd openbsd netbsd + +package fastwalk + +import "syscall" + +func direntInode(dirent *syscall.Dirent) uint64 { + return uint64(dirent.Fileno) +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go new file mode 100644 index 00000000..ab7fbc0a --- /dev/null +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go @@ -0,0 +1,14 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux darwin +// +build !appengine + +package fastwalk + +import "syscall" + +func direntInode(dirent *syscall.Dirent) uint64 { + return uint64(dirent.Ino) +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go new file mode 100644 index 00000000..a3b26a7b --- /dev/null +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go @@ -0,0 +1,13 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd openbsd netbsd + +package fastwalk + +import "syscall" + +func direntNamlen(dirent *syscall.Dirent) uint64 { + return uint64(dirent.Namlen) +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go new file mode 100644 index 00000000..e880d358 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go @@ -0,0 +1,29 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux +// +build !appengine + +package fastwalk + +import ( + "bytes" + "syscall" + "unsafe" +) + +func direntNamlen(dirent *syscall.Dirent) uint64 { + const fixedHdr = uint16(unsafe.Offsetof(syscall.Dirent{}.Name)) + nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0])) + const nameBufLen = uint16(len(nameBuf)) + limit := dirent.Reclen - fixedHdr + if limit > nameBufLen { + limit = nameBufLen + } + nameLen := bytes.IndexByte(nameBuf[:limit], 0) + if nameLen < 0 { + panic("failed to find terminating 0 byte in dirent") + } + return uint64(nameLen) +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go new file mode 100644 index 00000000..a906b875 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go @@ -0,0 +1,37 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build appengine !linux,!darwin,!freebsd,!openbsd,!netbsd + +package fastwalk + +import ( + "io/ioutil" + "os" +) + +// readDir calls fn for each directory entry in dirName. +// It does not descend into directories or follow symlinks. +// If fn returns a non-nil error, readDir returns with that error +// immediately. +func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error { + fis, err := ioutil.ReadDir(dirName) + if err != nil { + return err + } + skipFiles := false + for _, fi := range fis { + if fi.Mode().IsRegular() && skipFiles { + continue + } + if err := fn(dirName, fi.Name(), fi.Mode()&os.ModeType); err != nil { + if err == SkipFiles { + skipFiles = true + continue + } + return err + } + } + return nil +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go new file mode 100644 index 00000000..3369b1a0 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go @@ -0,0 +1,127 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux darwin freebsd openbsd netbsd +// +build !appengine + +package fastwalk + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +const blockSize = 8 << 10 + +// unknownFileMode is a sentinel (and bogus) os.FileMode +// value used to represent a syscall.DT_UNKNOWN Dirent.Type. +const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice + +func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error { + fd, err := syscall.Open(dirName, 0, 0) + if err != nil { + return &os.PathError{Op: "open", Path: dirName, Err: err} + } + defer syscall.Close(fd) + + // The buffer must be at least a block long. + buf := make([]byte, blockSize) // stack-allocated; doesn't escape + bufp := 0 // starting read position in buf + nbuf := 0 // end valid data in buf + skipFiles := false + for { + if bufp >= nbuf { + bufp = 0 + nbuf, err = syscall.ReadDirent(fd, buf) + if err != nil { + return os.NewSyscallError("readdirent", err) + } + if nbuf <= 0 { + return nil + } + } + consumed, name, typ := parseDirEnt(buf[bufp:nbuf]) + bufp += consumed + if name == "" || name == "." || name == ".." { + continue + } + // Fallback for filesystems (like old XFS) that don't + // support Dirent.Type and have DT_UNKNOWN (0) there + // instead. + if typ == unknownFileMode { + fi, err := os.Lstat(dirName + "/" + name) + if err != nil { + // It got deleted in the meantime. + if os.IsNotExist(err) { + continue + } + return err + } + typ = fi.Mode() & os.ModeType + } + if skipFiles && typ.IsRegular() { + continue + } + if err := fn(dirName, name, typ); err != nil { + if err == SkipFiles { + skipFiles = true + continue + } + return err + } + } +} + +func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) { + // golang.org/issue/15653 + dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0])) + if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v { + panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v)) + } + if len(buf) < int(dirent.Reclen) { + panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen)) + } + consumed = int(dirent.Reclen) + if direntInode(dirent) == 0 { // File absent in directory. + return + } + switch dirent.Type { + case syscall.DT_REG: + typ = 0 + case syscall.DT_DIR: + typ = os.ModeDir + case syscall.DT_LNK: + typ = os.ModeSymlink + case syscall.DT_BLK: + typ = os.ModeDevice + case syscall.DT_FIFO: + typ = os.ModeNamedPipe + case syscall.DT_SOCK: + typ = os.ModeSocket + case syscall.DT_UNKNOWN: + typ = unknownFileMode + default: + // Skip weird things. + // It's probably a DT_WHT (http://lwn.net/Articles/325369/) + // or something. Revisit if/when this package is moved outside + // of goimports. goimports only cares about regular files, + // symlinks, and directories. + return + } + + nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0])) + nameLen := direntNamlen(dirent) + + // Special cases for common things: + if nameLen == 1 && nameBuf[0] == '.' { + name = "." + } else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' { + name = ".." + } else { + name = string(nameBuf[:nameLen]) + } + return +} diff --git a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go new file mode 100644 index 00000000..9a61bdbf --- /dev/null +++ b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go @@ -0,0 +1,270 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gopathwalk is like filepath.Walk but specialized for finding Go +// packages, particularly in $GOPATH and $GOROOT. +package gopathwalk + +import ( + "bufio" + "bytes" + "fmt" + "go/build" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + "time" + + "golang.org/x/tools/internal/fastwalk" +) + +// Options controls the behavior of a Walk call. +type Options struct { + Debug bool // Enable debug logging + ModulesEnabled bool // Search module caches. Also disables legacy goimports ignore rules. +} + +// RootType indicates the type of a Root. +type RootType int + +const ( + RootUnknown RootType = iota + RootGOROOT + RootGOPATH + RootCurrentModule + RootModuleCache + RootOther +) + +// A Root is a starting point for a Walk. +type Root struct { + Path string + Type RootType +} + +// SrcDirsRoots returns the roots from build.Default.SrcDirs(). Not modules-compatible. +func SrcDirsRoots(ctx *build.Context) []Root { + var roots []Root + roots = append(roots, Root{filepath.Join(ctx.GOROOT, "src"), RootGOROOT}) + for _, p := range filepath.SplitList(ctx.GOPATH) { + roots = append(roots, Root{filepath.Join(p, "src"), RootGOPATH}) + } + return roots +} + +// Walk walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. +// For each package found, add will be called (concurrently) with the absolute +// paths of the containing source directory and the package directory. +// add will be called concurrently. +func Walk(roots []Root, add func(root Root, dir string), opts Options) { + WalkSkip(roots, add, func(Root, string) bool { return false }, opts) +} + +// WalkSkip walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. +// For each package found, add will be called (concurrently) with the absolute +// paths of the containing source directory and the package directory. +// For each directory that will be scanned, skip will be called (concurrently) +// with the absolute paths of the containing source directory and the directory. +// If skip returns false on a directory it will be processed. +// add will be called concurrently. +// skip will be called concurrently. +func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root, dir string) bool, opts Options) { + for _, root := range roots { + walkDir(root, add, skip, opts) + } +} + +func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) { + if _, err := os.Stat(root.Path); os.IsNotExist(err) { + if opts.Debug { + log.Printf("skipping nonexistent directory: %v", root.Path) + } + return + } + start := time.Now() + if opts.Debug { + log.Printf("gopathwalk: scanning %s", root.Path) + } + w := &walker{ + root: root, + add: add, + skip: skip, + opts: opts, + } + w.init() + if err := fastwalk.Walk(root.Path, w.walk); err != nil { + log.Printf("gopathwalk: scanning directory %v: %v", root.Path, err) + } + + if opts.Debug { + log.Printf("gopathwalk: scanned %s in %v", root.Path, time.Since(start)) + } +} + +// walker is the callback for fastwalk.Walk. +type walker struct { + root Root // The source directory to scan. + add func(Root, string) // The callback that will be invoked for every possible Go package dir. + skip func(Root, string) bool // The callback that will be invoked for every dir. dir is skipped if it returns true. + opts Options // Options passed to Walk by the user. + + ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files. +} + +// init initializes the walker based on its Options. +func (w *walker) init() { + var ignoredPaths []string + if w.root.Type == RootModuleCache { + ignoredPaths = []string{"cache"} + } + if !w.opts.ModulesEnabled && w.root.Type == RootGOPATH { + ignoredPaths = w.getIgnoredDirs(w.root.Path) + ignoredPaths = append(ignoredPaths, "v", "mod") + } + + for _, p := range ignoredPaths { + full := filepath.Join(w.root.Path, p) + if fi, err := os.Stat(full); err == nil { + w.ignoredDirs = append(w.ignoredDirs, fi) + if w.opts.Debug { + log.Printf("Directory added to ignore list: %s", full) + } + } else if w.opts.Debug { + log.Printf("Error statting ignored directory: %v", err) + } + } +} + +// getIgnoredDirs reads an optional config file at /.goimportsignore +// of relative directories to ignore when scanning for go files. +// The provided path is one of the $GOPATH entries with "src" appended. +func (w *walker) getIgnoredDirs(path string) []string { + file := filepath.Join(path, ".goimportsignore") + slurp, err := ioutil.ReadFile(file) + if w.opts.Debug { + if err != nil { + log.Print(err) + } else { + log.Printf("Read %s", file) + } + } + if err != nil { + return nil + } + + var ignoredDirs []string + bs := bufio.NewScanner(bytes.NewReader(slurp)) + for bs.Scan() { + line := strings.TrimSpace(bs.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + ignoredDirs = append(ignoredDirs, line) + } + return ignoredDirs +} + +func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool { + for _, ignoredDir := range w.ignoredDirs { + if os.SameFile(fi, ignoredDir) { + return true + } + } + if w.skip != nil { + // Check with the user specified callback. + return w.skip(w.root, dir) + } + return false +} + +func (w *walker) walk(path string, typ os.FileMode) error { + dir := filepath.Dir(path) + if typ.IsRegular() { + if dir == w.root.Path && (w.root.Type == RootGOROOT || w.root.Type == RootGOPATH) { + // Doesn't make sense to have regular files + // directly in your $GOPATH/src or $GOROOT/src. + return fastwalk.SkipFiles + } + if !strings.HasSuffix(path, ".go") { + return nil + } + + w.add(w.root, dir) + return fastwalk.SkipFiles + } + if typ == os.ModeDir { + base := filepath.Base(path) + if base == "" || base[0] == '.' || base[0] == '_' || + base == "testdata" || + (w.root.Type == RootGOROOT && w.opts.ModulesEnabled && base == "vendor") || + (!w.opts.ModulesEnabled && base == "node_modules") { + return filepath.SkipDir + } + fi, err := os.Lstat(path) + if err == nil && w.shouldSkipDir(fi, path) { + return filepath.SkipDir + } + return nil + } + if typ == os.ModeSymlink { + base := filepath.Base(path) + if strings.HasPrefix(base, ".#") { + // Emacs noise. + return nil + } + fi, err := os.Lstat(path) + if err != nil { + // Just ignore it. + return nil + } + if w.shouldTraverse(dir, fi) { + return fastwalk.TraverseLink + } + } + return nil +} + +// shouldTraverse reports whether the symlink fi, found in dir, +// should be followed. It makes sure symlinks were never visited +// before to avoid symlink loops. +func (w *walker) shouldTraverse(dir string, fi os.FileInfo) bool { + path := filepath.Join(dir, fi.Name()) + target, err := filepath.EvalSymlinks(path) + if err != nil { + return false + } + ts, err := os.Stat(target) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return false + } + if !ts.IsDir() { + return false + } + if w.shouldSkipDir(ts, dir) { + return false + } + // Check for symlink loops by statting each directory component + // and seeing if any are the same file as ts. + for { + parent := filepath.Dir(path) + if parent == path { + // Made it to the root without seeing a cycle. + // Use this symlink. + return true + } + parentInfo, err := os.Stat(parent) + if err != nil { + return false + } + if os.SameFile(ts, parentInfo) { + // Cycle. Don't traverse. + return false + } + path = parent + } + +} diff --git a/vendor/golang.org/x/tools/internal/semver/semver.go b/vendor/golang.org/x/tools/internal/semver/semver.go new file mode 100644 index 00000000..4af7118e --- /dev/null +++ b/vendor/golang.org/x/tools/internal/semver/semver.go @@ -0,0 +1,388 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package semver implements comparison of semantic version strings. +// In this package, semantic version strings must begin with a leading "v", +// as in "v1.0.0". +// +// The general form of a semantic version string accepted by this package is +// +// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] +// +// where square brackets indicate optional parts of the syntax; +// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; +// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers +// using only alphanumeric characters and hyphens; and +// all-numeric PRERELEASE identifiers must not have leading zeros. +// +// This package follows Semantic Versioning 2.0.0 (see semver.org) +// with two exceptions. First, it requires the "v" prefix. Second, it recognizes +// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) +// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. +package semver + +// parsed returns the parsed form of a semantic version string. +type parsed struct { + major string + minor string + patch string + short string + prerelease string + build string + err string +} + +// IsValid reports whether v is a valid semantic version string. +func IsValid(v string) bool { + _, ok := parse(v) + return ok +} + +// Canonical returns the canonical formatting of the semantic version v. +// It fills in any missing .MINOR or .PATCH and discards build metadata. +// Two semantic versions compare equal only if their canonical formattings +// are identical strings. +// The canonical invalid semantic version is the empty string. +func Canonical(v string) string { + p, ok := parse(v) + if !ok { + return "" + } + if p.build != "" { + return v[:len(v)-len(p.build)] + } + if p.short != "" { + return v + p.short + } + return v +} + +// Major returns the major version prefix of the semantic version v. +// For example, Major("v2.1.0") == "v2". +// If v is an invalid semantic version string, Major returns the empty string. +func Major(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return v[:1+len(pv.major)] +} + +// MajorMinor returns the major.minor version prefix of the semantic version v. +// For example, MajorMinor("v2.1.0") == "v2.1". +// If v is an invalid semantic version string, MajorMinor returns the empty string. +func MajorMinor(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + i := 1 + len(pv.major) + if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor { + return v[:j] + } + return v[:i] + "." + pv.minor +} + +// Prerelease returns the prerelease suffix of the semantic version v. +// For example, Prerelease("v2.1.0-pre+meta") == "-pre". +// If v is an invalid semantic version string, Prerelease returns the empty string. +func Prerelease(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return pv.prerelease +} + +// Build returns the build suffix of the semantic version v. +// For example, Build("v2.1.0+meta") == "+meta". +// If v is an invalid semantic version string, Build returns the empty string. +func Build(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return pv.build +} + +// Compare returns an integer comparing two versions according to +// according to semantic version precedence. +// The result will be 0 if v == w, -1 if v < w, or +1 if v > w. +// +// An invalid semantic version string is considered less than a valid one. +// All invalid semantic version strings compare equal to each other. +func Compare(v, w string) int { + pv, ok1 := parse(v) + pw, ok2 := parse(w) + if !ok1 && !ok2 { + return 0 + } + if !ok1 { + return -1 + } + if !ok2 { + return +1 + } + if c := compareInt(pv.major, pw.major); c != 0 { + return c + } + if c := compareInt(pv.minor, pw.minor); c != 0 { + return c + } + if c := compareInt(pv.patch, pw.patch); c != 0 { + return c + } + return comparePrerelease(pv.prerelease, pw.prerelease) +} + +// Max canonicalizes its arguments and then returns the version string +// that compares greater. +func Max(v, w string) string { + v = Canonical(v) + w = Canonical(w) + if Compare(v, w) > 0 { + return v + } + return w +} + +func parse(v string) (p parsed, ok bool) { + if v == "" || v[0] != 'v' { + p.err = "missing v prefix" + return + } + p.major, v, ok = parseInt(v[1:]) + if !ok { + p.err = "bad major version" + return + } + if v == "" { + p.minor = "0" + p.patch = "0" + p.short = ".0.0" + return + } + if v[0] != '.' { + p.err = "bad minor prefix" + ok = false + return + } + p.minor, v, ok = parseInt(v[1:]) + if !ok { + p.err = "bad minor version" + return + } + if v == "" { + p.patch = "0" + p.short = ".0" + return + } + if v[0] != '.' { + p.err = "bad patch prefix" + ok = false + return + } + p.patch, v, ok = parseInt(v[1:]) + if !ok { + p.err = "bad patch version" + return + } + if len(v) > 0 && v[0] == '-' { + p.prerelease, v, ok = parsePrerelease(v) + if !ok { + p.err = "bad prerelease" + return + } + } + if len(v) > 0 && v[0] == '+' { + p.build, v, ok = parseBuild(v) + if !ok { + p.err = "bad build" + return + } + } + if v != "" { + p.err = "junk on end" + ok = false + return + } + ok = true + return +} + +func parseInt(v string) (t, rest string, ok bool) { + if v == "" { + return + } + if v[0] < '0' || '9' < v[0] { + return + } + i := 1 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + if v[0] == '0' && i != 1 { + return + } + return v[:i], v[i:], true +} + +func parsePrerelease(v string) (t, rest string, ok bool) { + // "A pre-release version MAY be denoted by appending a hyphen and + // a series of dot separated identifiers immediately following the patch version. + // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. + // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." + if v == "" || v[0] != '-' { + return + } + i := 1 + start := 1 + for i < len(v) && v[i] != '+' { + if !isIdentChar(v[i]) && v[i] != '.' { + return + } + if v[i] == '.' { + if start == i || isBadNum(v[start:i]) { + return + } + start = i + 1 + } + i++ + } + if start == i || isBadNum(v[start:i]) { + return + } + return v[:i], v[i:], true +} + +func parseBuild(v string) (t, rest string, ok bool) { + if v == "" || v[0] != '+' { + return + } + i := 1 + start := 1 + for i < len(v) { + if !isIdentChar(v[i]) { + return + } + if v[i] == '.' { + if start == i { + return + } + start = i + 1 + } + i++ + } + if start == i { + return + } + return v[:i], v[i:], true +} + +func isIdentChar(c byte) bool { + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' +} + +func isBadNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) && i > 1 && v[0] == '0' +} + +func isNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) +} + +func compareInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} + +func comparePrerelease(x, y string) int { + // "When major, minor, and patch are equal, a pre-release version has + // lower precedence than a normal version. + // Example: 1.0.0-alpha < 1.0.0. + // Precedence for two pre-release versions with the same major, minor, + // and patch version MUST be determined by comparing each dot separated + // identifier from left to right until a difference is found as follows: + // identifiers consisting of only digits are compared numerically and + // identifiers with letters or hyphens are compared lexically in ASCII + // sort order. Numeric identifiers always have lower precedence than + // non-numeric identifiers. A larger set of pre-release fields has a + // higher precedence than a smaller set, if all of the preceding + // identifiers are equal. + // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < + // 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0." + if x == y { + return 0 + } + if x == "" { + return +1 + } + if y == "" { + return -1 + } + for x != "" && y != "" { + x = x[1:] // skip - or . + y = y[1:] // skip - or . + var dx, dy string + dx, x = nextIdent(x) + dy, y = nextIdent(y) + if dx != dy { + ix := isNum(dx) + iy := isNum(dy) + if ix != iy { + if ix { + return -1 + } else { + return +1 + } + } + if ix { + if len(dx) < len(dy) { + return -1 + } + if len(dx) > len(dy) { + return +1 + } + } + if dx < dy { + return -1 + } else { + return +1 + } + } + } + if x == "" { + return -1 + } else { + return +1 + } +} + +func nextIdent(x string) (dx, rest string) { + i := 0 + for i < len(x) && x[i] != '.' { + i++ + } + return x[:i], x[i:] +} diff --git a/vendor/golang.org/x/tools/internal/span/parse.go b/vendor/golang.org/x/tools/internal/span/parse.go new file mode 100644 index 00000000..b3f268a3 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/parse.go @@ -0,0 +1,100 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package span + +import ( + "strconv" + "strings" + "unicode/utf8" +) + +// Parse returns the location represented by the input. +// All inputs are valid locations, as they can always be a pure filename. +// The returned span will be normalized, and thus if printed may produce a +// different string. +func Parse(input string) Span { + // :0:0#0-0:0#0 + valid := input + var hold, offset int + hadCol := false + suf := rstripSuffix(input) + if suf.sep == "#" { + offset = suf.num + suf = rstripSuffix(suf.remains) + } + if suf.sep == ":" { + valid = suf.remains + hold = suf.num + hadCol = true + suf = rstripSuffix(suf.remains) + } + switch { + case suf.sep == ":": + return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), Point{}) + case suf.sep == "-": + // we have a span, fall out of the case to continue + default: + // separator not valid, rewind to either the : or the start + return New(NewURI(valid), NewPoint(hold, 0, offset), Point{}) + } + // only the span form can get here + // at this point we still don't know what the numbers we have mean + // if have not yet seen a : then we might have either a line or a column depending + // on whether start has a column or not + // we build an end point and will fix it later if needed + end := NewPoint(suf.num, hold, offset) + hold, offset = 0, 0 + suf = rstripSuffix(suf.remains) + if suf.sep == "#" { + offset = suf.num + suf = rstripSuffix(suf.remains) + } + if suf.sep != ":" { + // turns out we don't have a span after all, rewind + return New(NewURI(valid), end, Point{}) + } + valid = suf.remains + hold = suf.num + suf = rstripSuffix(suf.remains) + if suf.sep != ":" { + // line#offset only + return New(NewURI(valid), NewPoint(hold, 0, offset), end) + } + // we have a column, so if end only had one number, it is also the column + if !hadCol { + end = NewPoint(suf.num, end.v.Line, end.v.Offset) + } + return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), end) +} + +type suffix struct { + remains string + sep string + num int +} + +func rstripSuffix(input string) suffix { + if len(input) == 0 { + return suffix{"", "", -1} + } + remains := input + num := -1 + // first see if we have a number at the end + last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' }) + if last >= 0 && last < len(remains)-1 { + number, err := strconv.ParseInt(remains[last+1:], 10, 64) + if err == nil { + num = int(number) + remains = remains[:last+1] + } + } + // now see if we have a trailing separator + r, w := utf8.DecodeLastRuneInString(remains) + if r != ':' && r != '#' && r == '#' { + return suffix{input, "", -1} + } + remains = remains[:len(remains)-w] + return suffix{remains, string(r), num} +} diff --git a/vendor/golang.org/x/tools/internal/span/span.go b/vendor/golang.org/x/tools/internal/span/span.go new file mode 100644 index 00000000..4d2ad098 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/span.go @@ -0,0 +1,285 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package span contains support for representing with positions and ranges in +// text files. +package span + +import ( + "encoding/json" + "fmt" + "path" +) + +// Span represents a source code range in standardized form. +type Span struct { + v span +} + +// Point represents a single point within a file. +// In general this should only be used as part of a Span, as on its own it +// does not carry enough information. +type Point struct { + v point +} + +type span struct { + URI URI `json:"uri"` + Start point `json:"start"` + End point `json:"end"` +} + +type point struct { + Line int `json:"line"` + Column int `json:"column"` + Offset int `json:"offset"` +} + +// Invalid is a span that reports false from IsValid +var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}} + +var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}} + +// Converter is the interface to an object that can convert between line:column +// and offset forms for a single file. +type Converter interface { + //ToPosition converts from an offset to a line:column pair. + ToPosition(offset int) (int, int, error) + //ToOffset converts from a line:column pair to an offset. + ToOffset(line, col int) (int, error) +} + +func New(uri URI, start Point, end Point) Span { + s := Span{v: span{URI: uri, Start: start.v, End: end.v}} + s.v.clean() + return s +} + +func NewPoint(line, col, offset int) Point { + p := Point{v: point{Line: line, Column: col, Offset: offset}} + p.v.clean() + return p +} + +func Compare(a, b Span) int { + if r := CompareURI(a.URI(), b.URI()); r != 0 { + return r + } + if r := comparePoint(a.v.Start, b.v.Start); r != 0 { + return r + } + return comparePoint(a.v.End, b.v.End) +} + +func ComparePoint(a, b Point) int { + return comparePoint(a.v, b.v) +} + +func comparePoint(a, b point) int { + if !a.hasPosition() { + if a.Offset < b.Offset { + return -1 + } + if a.Offset > b.Offset { + return 1 + } + return 0 + } + if a.Line < b.Line { + return -1 + } + if a.Line > b.Line { + return 1 + } + if a.Column < b.Column { + return -1 + } + if a.Column > b.Column { + return 1 + } + return 0 +} + +func (s Span) HasPosition() bool { return s.v.Start.hasPosition() } +func (s Span) HasOffset() bool { return s.v.Start.hasOffset() } +func (s Span) IsValid() bool { return s.v.Start.isValid() } +func (s Span) IsPoint() bool { return s.v.Start == s.v.End } +func (s Span) URI() URI { return s.v.URI } +func (s Span) Start() Point { return Point{s.v.Start} } +func (s Span) End() Point { return Point{s.v.End} } +func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) } +func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) } + +func (p Point) HasPosition() bool { return p.v.hasPosition() } +func (p Point) HasOffset() bool { return p.v.hasOffset() } +func (p Point) IsValid() bool { return p.v.isValid() } +func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) } +func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) } +func (p Point) Line() int { + if !p.v.hasPosition() { + panic(fmt.Errorf("position not set in %v", p.v)) + } + return p.v.Line +} +func (p Point) Column() int { + if !p.v.hasPosition() { + panic(fmt.Errorf("position not set in %v", p.v)) + } + return p.v.Column +} +func (p Point) Offset() int { + if !p.v.hasOffset() { + panic(fmt.Errorf("offset not set in %v", p.v)) + } + return p.v.Offset +} + +func (p point) hasPosition() bool { return p.Line > 0 } +func (p point) hasOffset() bool { return p.Offset >= 0 } +func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() } +func (p point) isZero() bool { + return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0) +} + +func (s *span) clean() { + //this presumes the points are already clean + if !s.End.isValid() || (s.End == point{}) { + s.End = s.Start + } +} + +func (p *point) clean() { + if p.Line < 0 { + p.Line = 0 + } + if p.Column <= 0 { + if p.Line > 0 { + p.Column = 1 + } else { + p.Column = 0 + } + } + if p.Offset == 0 && (p.Line > 1 || p.Column > 1) { + p.Offset = -1 + } +} + +// Format implements fmt.Formatter to print the Location in a standard form. +// The format produced is one that can be read back in using Parse. +func (s Span) Format(f fmt.State, c rune) { + fullForm := f.Flag('+') + preferOffset := f.Flag('#') + // we should always have a uri, simplify if it is file format + //TODO: make sure the end of the uri is unambiguous + uri := string(s.v.URI) + if c == 'f' { + uri = path.Base(uri) + } else if !fullForm { + uri = s.v.URI.Filename() + } + fmt.Fprint(f, uri) + if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) { + return + } + // see which bits of start to write + printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition()) + printLine := s.HasPosition() && (fullForm || !printOffset) + printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1)) + fmt.Fprint(f, ":") + if printLine { + fmt.Fprintf(f, "%d", s.v.Start.Line) + } + if printColumn { + fmt.Fprintf(f, ":%d", s.v.Start.Column) + } + if printOffset { + fmt.Fprintf(f, "#%d", s.v.Start.Offset) + } + // start is written, do we need end? + if s.IsPoint() { + return + } + // we don't print the line if it did not change + printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line) + fmt.Fprint(f, "-") + if printLine { + fmt.Fprintf(f, "%d", s.v.End.Line) + } + if printColumn { + if printLine { + fmt.Fprint(f, ":") + } + fmt.Fprintf(f, "%d", s.v.End.Column) + } + if printOffset { + fmt.Fprintf(f, "#%d", s.v.End.Offset) + } +} + +func (s Span) WithPosition(c Converter) (Span, error) { + if err := s.update(c, true, false); err != nil { + return Span{}, err + } + return s, nil +} + +func (s Span) WithOffset(c Converter) (Span, error) { + if err := s.update(c, false, true); err != nil { + return Span{}, err + } + return s, nil +} + +func (s Span) WithAll(c Converter) (Span, error) { + if err := s.update(c, true, true); err != nil { + return Span{}, err + } + return s, nil +} + +func (s *Span) update(c Converter, withPos, withOffset bool) error { + if !s.IsValid() { + return fmt.Errorf("cannot add information to an invalid span") + } + if withPos && !s.HasPosition() { + if err := s.v.Start.updatePosition(c); err != nil { + return err + } + if s.v.End.Offset == s.v.Start.Offset { + s.v.End = s.v.Start + } else if err := s.v.End.updatePosition(c); err != nil { + return err + } + } + if withOffset && (!s.HasOffset() || (s.v.End.hasPosition() && !s.v.End.hasOffset())) { + if err := s.v.Start.updateOffset(c); err != nil { + return err + } + if s.v.End.Line == s.v.Start.Line && s.v.End.Column == s.v.Start.Column { + s.v.End.Offset = s.v.Start.Offset + } else if err := s.v.End.updateOffset(c); err != nil { + return err + } + } + return nil +} + +func (p *point) updatePosition(c Converter) error { + line, col, err := c.ToPosition(p.Offset) + if err != nil { + return err + } + p.Line = line + p.Column = col + return nil +} + +func (p *point) updateOffset(c Converter) error { + offset, err := c.ToOffset(p.Line, p.Column) + if err != nil { + return err + } + p.Offset = offset + return nil +} diff --git a/vendor/golang.org/x/tools/internal/span/token.go b/vendor/golang.org/x/tools/internal/span/token.go new file mode 100644 index 00000000..ce44541b --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/token.go @@ -0,0 +1,151 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package span + +import ( + "fmt" + "go/token" +) + +// Range represents a source code range in token.Pos form. +// It also carries the FileSet that produced the positions, so that it is +// self contained. +type Range struct { + FileSet *token.FileSet + Start token.Pos + End token.Pos +} + +// TokenConverter is a Converter backed by a token file set and file. +// It uses the file set methods to work out the conversions, which +// makes it fast and does not require the file contents. +type TokenConverter struct { + fset *token.FileSet + file *token.File +} + +// NewRange creates a new Range from a FileSet and two positions. +// To represent a point pass a 0 as the end pos. +func NewRange(fset *token.FileSet, start, end token.Pos) Range { + return Range{ + FileSet: fset, + Start: start, + End: end, + } +} + +// NewTokenConverter returns an implementation of Converter backed by a +// token.File. +func NewTokenConverter(fset *token.FileSet, f *token.File) *TokenConverter { + return &TokenConverter{fset: fset, file: f} +} + +// NewContentConverter returns an implementation of Converter for the +// given file content. +func NewContentConverter(filename string, content []byte) *TokenConverter { + fset := token.NewFileSet() + f := fset.AddFile(filename, -1, len(content)) + f.SetLinesForContent(content) + return &TokenConverter{fset: fset, file: f} +} + +// IsPoint returns true if the range represents a single point. +func (r Range) IsPoint() bool { + return r.Start == r.End +} + +// Span converts a Range to a Span that represents the Range. +// It will fill in all the members of the Span, calculating the line and column +// information. +func (r Range) Span() (Span, error) { + f := r.FileSet.File(r.Start) + if f == nil { + return Span{}, fmt.Errorf("file not found in FileSet") + } + s := Span{v: span{URI: FileURI(f.Name())}} + var err error + s.v.Start.Offset, err = offset(f, r.Start) + if err != nil { + return Span{}, err + } + if r.End.IsValid() { + s.v.End.Offset, err = offset(f, r.End) + if err != nil { + return Span{}, err + } + } + s.v.Start.clean() + s.v.End.clean() + s.v.clean() + converter := NewTokenConverter(r.FileSet, f) + return s.WithPosition(converter) +} + +// offset is a copy of the Offset function in go/token, but with the adjustment +// that it does not panic on invalid positions. +func offset(f *token.File, pos token.Pos) (int, error) { + if int(pos) < f.Base() || int(pos) > f.Base()+f.Size() { + return 0, fmt.Errorf("invalid pos") + } + return int(pos) - f.Base(), nil +} + +// Range converts a Span to a Range that represents the Span for the supplied +// File. +func (s Span) Range(converter *TokenConverter) (Range, error) { + s, err := s.WithOffset(converter) + if err != nil { + return Range{}, err + } + // go/token will panic if the offset is larger than the file's size, + // so check here to avoid panicking. + if s.Start().Offset() > converter.file.Size() { + return Range{}, fmt.Errorf("start offset %v is past the end of the file %v", s.Start(), converter.file.Size()) + } + if s.End().Offset() > converter.file.Size() { + return Range{}, fmt.Errorf("end offset %v is past the end of the file %v", s.End(), converter.file.Size()) + } + return Range{ + FileSet: converter.fset, + Start: converter.file.Pos(s.Start().Offset()), + End: converter.file.Pos(s.End().Offset()), + }, nil +} + +func (l *TokenConverter) ToPosition(offset int) (int, int, error) { + if offset > l.file.Size() { + return 0, 0, fmt.Errorf("offset %v is past the end of the file %v", offset, l.file.Size()) + } + pos := l.file.Pos(offset) + p := l.fset.Position(pos) + if offset == l.file.Size() { + return p.Line + 1, 1, nil + } + return p.Line, p.Column, nil +} + +func (l *TokenConverter) ToOffset(line, col int) (int, error) { + if line < 0 { + return -1, fmt.Errorf("line is not valid") + } + lineMax := l.file.LineCount() + 1 + if line > lineMax { + return -1, fmt.Errorf("line is beyond end of file %v", lineMax) + } else if line == lineMax { + if col > 1 { + return -1, fmt.Errorf("column is beyond end of file") + } + // at the end of the file, allowing for a trailing eol + return l.file.Size(), nil + } + pos := lineStart(l.file, line) + if !pos.IsValid() { + return -1, fmt.Errorf("line is not in file") + } + // we assume that column is in bytes here, and that the first byte of a + // line is at column 1 + pos += token.Pos(col - 1) + return offset(l.file, pos) +} diff --git a/vendor/golang.org/x/tools/internal/span/token111.go b/vendor/golang.org/x/tools/internal/span/token111.go new file mode 100644 index 00000000..bf7a5406 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/token111.go @@ -0,0 +1,39 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.12 + +package span + +import ( + "go/token" +) + +// lineStart is the pre-Go 1.12 version of (*token.File).LineStart. For Go +// versions <= 1.11, we borrow logic from the analysisutil package. +// TODO(rstambler): Delete this file when we no longer support Go 1.11. +func lineStart(f *token.File, line int) token.Pos { + // Use binary search to find the start offset of this line. + + min := 0 // inclusive + max := f.Size() // exclusive + for { + offset := (min + max) / 2 + pos := f.Pos(offset) + posn := f.Position(pos) + if posn.Line == line { + return pos - (token.Pos(posn.Column) - 1) + } + + if min+1 >= max { + return token.NoPos + } + + if posn.Line < line { + min = offset + } else { + max = offset + } + } +} diff --git a/vendor/golang.org/x/tools/internal/span/token112.go b/vendor/golang.org/x/tools/internal/span/token112.go new file mode 100644 index 00000000..017aec9c --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/token112.go @@ -0,0 +1,16 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.12 + +package span + +import ( + "go/token" +) + +// TODO(rstambler): Delete this file when we no longer support Go 1.11. +func lineStart(f *token.File, line int) token.Pos { + return f.LineStart(line) +} diff --git a/vendor/golang.org/x/tools/internal/span/uri.go b/vendor/golang.org/x/tools/internal/span/uri.go new file mode 100644 index 00000000..e05a9e6e --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/uri.go @@ -0,0 +1,152 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package span + +import ( + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "unicode" +) + +const fileScheme = "file" + +// URI represents the full URI for a file. +type URI string + +// Filename returns the file path for the given URI. +// It is an error to call this on a URI that is not a valid filename. +func (uri URI) Filename() string { + filename, err := filename(uri) + if err != nil { + panic(err) + } + return filepath.FromSlash(filename) +} + +func filename(uri URI) (string, error) { + if uri == "" { + return "", nil + } + u, err := url.ParseRequestURI(string(uri)) + if err != nil { + return "", err + } + if u.Scheme != fileScheme { + return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri) + } + if isWindowsDriveURI(u.Path) { + u.Path = u.Path[1:] + } + return u.Path, nil +} + +// NewURI returns a span URI for the string. +// It will attempt to detect if the string is a file path or uri. +func NewURI(s string) URI { + if u, err := url.PathUnescape(s); err == nil { + s = u + } + if strings.HasPrefix(s, fileScheme+"://") { + return URI(s) + } + return FileURI(s) +} + +func CompareURI(a, b URI) int { + if equalURI(a, b) { + return 0 + } + if a < b { + return -1 + } + return 1 +} + +func equalURI(a, b URI) bool { + if a == b { + return true + } + // If we have the same URI basename, we may still have the same file URIs. + if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) { + return false + } + fa, err := filename(a) + if err != nil { + return false + } + fb, err := filename(b) + if err != nil { + return false + } + // Stat the files to check if they are equal. + infoa, err := os.Stat(filepath.FromSlash(fa)) + if err != nil { + return false + } + infob, err := os.Stat(filepath.FromSlash(fb)) + if err != nil { + return false + } + return os.SameFile(infoa, infob) +} + +// FileURI returns a span URI for the supplied file path. +// It will always have the file scheme. +func FileURI(path string) URI { + if path == "" { + return "" + } + // Handle standard library paths that contain the literal "$GOROOT". + // TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT. + const prefix = "$GOROOT" + if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) { + suffix := path[len(prefix):] + path = runtime.GOROOT() + suffix + } + if !isWindowsDrivePath(path) { + if abs, err := filepath.Abs(path); err == nil { + path = abs + } + } + // Check the file path again, in case it became absolute. + if isWindowsDrivePath(path) { + path = "/" + path + } + path = filepath.ToSlash(path) + u := url.URL{ + Scheme: fileScheme, + Path: path, + } + uri := u.String() + if unescaped, err := url.PathUnescape(uri); err == nil { + uri = unescaped + } + return URI(uri) +} + +// isWindowsDrivePath returns true if the file path is of the form used by +// Windows. We check if the path begins with a drive letter, followed by a ":". +func isWindowsDrivePath(path string) bool { + if len(path) < 4 { + return false + } + return unicode.IsLetter(rune(path[0])) && path[1] == ':' +} + +// isWindowsDriveURI returns true if the file URI is of the format used by +// Windows URIs. The url.Parse package does not specially handle Windows paths +// (see https://golang.org/issue/6027). We check if the URI path has +// a drive prefix (e.g. "/C:"). If so, we trim the leading "/". +func isWindowsDriveURI(uri string) bool { + if len(uri) < 4 { + return false + } + return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':' +} diff --git a/vendor/golang.org/x/tools/internal/span/utf16.go b/vendor/golang.org/x/tools/internal/span/utf16.go new file mode 100644 index 00000000..561b3fa5 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/utf16.go @@ -0,0 +1,94 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package span + +import ( + "fmt" + "unicode/utf16" + "unicode/utf8" +) + +// ToUTF16Column calculates the utf16 column expressed by the point given the +// supplied file contents. +// This is used to convert from the native (always in bytes) column +// representation and the utf16 counts used by some editors. +func ToUTF16Column(p Point, content []byte) (int, error) { + if content == nil { + return -1, fmt.Errorf("ToUTF16Column: missing content") + } + if !p.HasPosition() { + return -1, fmt.Errorf("ToUTF16Column: point is missing position") + } + if !p.HasOffset() { + return -1, fmt.Errorf("ToUTF16Column: point is missing offset") + } + offset := p.Offset() // 0-based + colZero := p.Column() - 1 // 0-based + if colZero == 0 { + // 0-based column 0, so it must be chr 1 + return 1, nil + } else if colZero < 0 { + return -1, fmt.Errorf("ToUTF16Column: column is invalid (%v)", colZero) + } + // work out the offset at the start of the line using the column + lineOffset := offset - colZero + if lineOffset < 0 || offset > len(content) { + return -1, fmt.Errorf("ToUTF16Column: offsets %v-%v outside file contents (%v)", lineOffset, offset, len(content)) + } + // Use the offset to pick out the line start. + // This cannot panic: offset > len(content) and lineOffset < offset. + start := content[lineOffset:] + + // Now, truncate down to the supplied column. + start = start[:colZero] + + // and count the number of utf16 characters + // in theory we could do this by hand more efficiently... + return len(utf16.Encode([]rune(string(start)))) + 1, nil +} + +// FromUTF16Column advances the point by the utf16 character offset given the +// supplied line contents. +// This is used to convert from the utf16 counts used by some editors to the +// native (always in bytes) column representation. +func FromUTF16Column(p Point, chr int, content []byte) (Point, error) { + if !p.HasOffset() { + return Point{}, fmt.Errorf("FromUTF16Column: point is missing offset") + } + // if chr is 1 then no adjustment needed + if chr <= 1 { + return p, nil + } + if p.Offset() >= len(content) { + return p, fmt.Errorf("FromUTF16Column: offset (%v) greater than length of content (%v)", p.Offset(), len(content)) + } + remains := content[p.Offset():] + // scan forward the specified number of characters + for count := 1; count < chr; count++ { + if len(remains) <= 0 { + return Point{}, fmt.Errorf("FromUTF16Column: chr goes beyond the content") + } + r, w := utf8.DecodeRune(remains) + if r == '\n' { + // Per the LSP spec: + // + // > If the character value is greater than the line length it + // > defaults back to the line length. + break + } + remains = remains[w:] + if r >= 0x10000 { + // a two point rune + count++ + // if we finished in a two point rune, do not advance past the first + if count >= chr { + break + } + } + p.v.Column += w + p.v.Offset += w + } + return p, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 00000000..3109da21 --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,9 @@ +# golang.org/x/tools v0.0.0-20191030225452-7871c2d76733 +golang.org/x/tools/go/gcexportdata +golang.org/x/tools/go/internal/gcimporter +golang.org/x/tools/go/internal/packagesdriver +golang.org/x/tools/go/packages +golang.org/x/tools/internal/fastwalk +golang.org/x/tools/internal/gopathwalk +golang.org/x/tools/internal/semver +golang.org/x/tools/internal/span